canvas.width/height与css width/height必须严格分离:前者设定绘图缓冲区像素数,后者仅控制显示尺寸;不一致会导致拉伸模糊、线条变粗、ispointinpath失效等问题,需结合devicepixelratio校准并禁用插值。

canvas.width/height 和 CSS width/height 必须严格分离
HTML 的 width 和 height 属性不是样式,而是直接设定绘图缓冲区的像素数;而 CSS 的 style.width/style.height 只控制元素在页面上的显示尺寸。两者不一致时,浏览器会强行拉伸或压缩位图——这是模糊的根源,和抗锯齿无关。
常见错误现象:ctx.lineWidth = 1 画出来像 2 像素宽、圆形边缘发虚、isPointInPath() 判断失效、导出 PNG 尺寸不对。
- 只设
style.width和style.height,没动canvas.width/canvas.height→ 缓冲区仍是默认 300×150,内容被拉伸 - 用 JS 改
canvas.style.width却忘了同步改canvas.width→ 渲染分辨率没变,但显示区域变了 - 响应式容器里只监听
resize,却没取getBoundingClientRect()而用offsetWidth→ 后者受父级transform干扰,尺寸不准
非同比例缩放(比如宽高缩放系数不同)时必须禁用插值
当 CSS 设置了非 1:1 的宽高比(例如 style="width:400px;height:200px"),而你又按 dpr 放大了 canvas.width/canvas.height,此时浏览器仍可能对缓冲区做非均匀拉伸。关键是要关掉图像平滑:
- 设置
ctx.imageSmoothingEnabled = false(对drawImage有效) - 给 canvas 元素加 CSS:
image-rendering: pixelated(Chrome/Safari)、image-rendering: -webkit-optimize-contrast(WebKit)、image-rendering: -moz-crisp-edges(Firefox) - 避免使用
ctx.scale(x, y)且x !== y,否则坐标系扭曲,鼠标换算复杂度陡增
注意:image-rendering: pixelated 在 Safari 15.4+ 才稳定支持,老版本需 fallback 到 -webkit-optimize-contrast。
高 DPI 设备下必须用 devicePixelRatio 校准缓冲区
在 MacBook、Windows 缩放 125%/150%、iPhone 等设备上,window.devicePixelRatio > 1。若只按 CSS 尺寸设 canvas.width/canvas.height,缓冲区物理像素不足,必然模糊。
正确做法是:
- 用
canvas.getBoundingClientRect()获取当前显示宽高(真实 CSS 像素) - 计算
const dpr = window.devicePixelRatio || 1 - 设
canvas.width = rect.width * dpr、canvas.height = rect.height * dpr - 再设
canvas.style.width = rect.width + "px"、canvas.style.height = rect.height + "px" - 最后调用
ctx.scale(dpr, dpr),让绘图逻辑仍按 CSS 像素写(否则所有坐标/尺寸都要乘dpr)
canvas.width 赋值会清空画布,这是预期行为,不是 bug —— 重绘必须跟上。
叠加 DOM 元素时错位,本质是坐标系没对齐
Canvas 上层盖 tooltip、SVG 图标、文字标签却偏移几像素?不是 z-index 或 position 写错了,而是 Canvas 内部坐标系和 CSS 像素没对齐。
- Canvas 的
getBoundingClientRect().left/top返回的是 CSS 像素位置,但绘制时用了放大后的缓冲区 → 实际渲染点偏移 - 鼠标事件的
clientX/clientY是 CSS 像素,要换算成缓冲区坐标:const x = (e.clientX - rect.left) * dpr - 叠加 DOM 必须加
transform: translateZ(0)强制 GPU 层,否则可能被 Canvas 合成层遮挡 - Canvas 元素建议设
pointer-events: none,把点击穿透给底层 SVG 或其它交互层
最易被忽略的一点:window.devicePixelRatio 会在用户切换显示器、调整系统缩放时动态变化,必须监听 matchMedia 变更事件,不能只在初始化时读一次。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











