canvas 的 width/height 必须等于 clientwidth/clientheight,否则内容拉伸模糊、坐标错位;高 dpi 屏需乘 devicepixelratio 并 ctx.scale,resizeobserver 监听自身且加空值防护。

Canvas 的 width 和 height 属性必须等于其当前 CSS 渲染尺寸(即 clientWidth/clientHeight),否则内容会拉伸、模糊或点击失灵——仅靠 CSS 设置 width: 100% 是无效的,必须用 JS 同步。
为什么 canvas.width/height 必须等于 clientWidth/clientHeight
Canvas 是替换元素,它的绘图表面由 width 和 height 属性决定,不是 CSS 样式。当 CSS 把 Canvas 拉宽到 800px,但 canvas.width 还是 400,浏览器就得把 400 个像素“撑开”填满 800px,结果就是模糊、坐标错位、ctx.fillRect(200, 150, ...) 出现在错误位置。
常见错误现象:
-
getBoundingClientRect().width是 600,但canvas.width是 400 → 内容被横向拉伸 1.5 倍 - 鼠标点击位置和
ctx.isPointInPath()判断不一致 - 在 flex 容器里 Canvas 看起来“只显示左上角”,实际是分辨率没跟上
用 ResizeObserver 同步 width/height 的最小可行代码
监听目标必须是 Canvas 自身(不是父容器),且每次回调都要重设 width/height,再清空重绘:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
<p>const resize = () => {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw(); // 你的绘制逻辑
};</p><p>new ResizeObserver(resize).observe(canvas);
</p>
关键点:
- 不要用
window.addEventListener('resize', ...):它不响应 flex/grid 内部尺寸变化 - 不要只改 CSS:哪怕写了
style="width:100%;height:100%",也得 JS 干预 - 重绘必须放在
resize回调里:否则新尺寸下图形还是按旧分辨率画的
高 DPI 屏幕下必须乘以 devicePixelRatio
在 Retina 或 Windows 高缩放比设备上,仅设 canvas.width = canvas.clientWidth 仍会模糊,因为物理像素不够。此时要放大内部分辨率:
const dpr = window.devicePixelRatio || 1; canvas.width = Math.floor(canvas.clientWidth * dpr); canvas.height = Math.floor(canvas.clientHeight * dpr); ctx.scale(dpr, dpr);
注意:
- 必须同时设置
ctx.scale(dpr, dpr),否则所有坐标都会偏移(比如fillRect(100, 100, ...)会跑到右下角) - 缩放后,
canvas.style.width和canvas.style.height应保持为clientWidth/clientHeight,不能动 - 移动端 Safari 对
devicePixelRatio > 2有渲染限制,建议上限设为Math.min(2, dpr)
容易被忽略的边界情况
ResizeObserver 回调可能高频触发,但 Canvas 重绘成本高;另外某些场景下 clientWidth 可能为 0(如组件刚挂载、父容器 display: none):
- 加空值防护:
if (!canvas.clientWidth || !canvas.clientHeight) return; - 避免重复重绘:缓存上一次
canvas.width,仅当变化超过 1px 才执行重绘 - React/Vue 组件卸载时记得调用
observer.unobserve(canvas)或observer.disconnect(),否则内存泄漏 - 如果 Canvas 在 iframe 里,需监听 iframe.contentWindow 上的 ResizeObserver,或用
postMessage通知父页
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











