真正解决canvas高清屏模糊需同步控制物理像素尺寸、css显示尺寸和绘图上下文缩放比例;仅设canvas.width/height会清空画布且不改变缩放,必须按四步重设:读css尺寸、计算物理像素、css压回视觉尺寸、ctx.settransform(dpr,dpr)。

直接改 canvas.width 不等于缩放,而是重置缓冲区——它会清空画布、改变坐标映射精度,且不解决高清屏模糊问题。真正要做的是同步控制三件事:物理像素尺寸、CSS显示尺寸、绘图上下文缩放比例。
为什么只设 width 和 height 属性还是糊
因为 canvas.width 和 canvas.height 只设了缓冲区大小,但没告诉绘图上下文“1个逻辑单位该占几个物理像素”。在 devicePixelRatio === 2 的设备上,若只写 canvas.width = 400,实际只有 400 个物理像素宽,却要撑满 400px CSS 宽度,每个 CSS 像素被迫用 1 个物理像素填充,细节全丢。
- 浏览器不会报错,只是边缘发虚、文字锯齿、
ctx.lineWidth = 1看起来像 2px -
getBoundingClientRect().width返回的是 CSS 像素,不能直接赋给canvas.width - 用 CSS 设置
width: 100%而不改canvas.width,等于强制拉伸默认 300×150 缓冲区,必糊
resizeCanvas() 函数必须包含的四步
每次容器尺寸变化时,重设画布必须按顺序执行这四步,缺一不可:
- 读取容器 CSS 尺寸:
const rect = container.getBoundingClientRect() - 计算物理像素尺寸:
canvas.width = rect.width * dpr、canvas.height = rect.height * dpr(dpr = window.devicePixelRatio || 1) - 用 CSS 把画布“压回”视觉尺寸:
canvas.style.width = rect.width + 'px'、canvas.style.height = rect.height + 'px' - 重置绘图上下文缩放:
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)(比ctx.scale()更安全,避免叠加)
注意:canvas.width = ... 会自动清空画布,所以重设后必须立刻重绘,或先 ctx.save() 再 ctx.restore() 保护状态(仅当需保留变换时)。
导出 toDataURL() 还是糊?检查这三个地方
即使重设了尺寸和缩放,导出仍糊,大概率是以下任一操作漏了或做错了:
- 调用
toDataURL()前没确保ctx处于正确缩放状态——比如在resize回调里多次ctx.scale(dpr, dpr),导致缩放叠加 - 用了
ctx.scale()却没在绘制前ctx.save(),重设后忘记ctx.restore(),后续绘图坐标错乱 - 导出前没清除旧内容:
ctx.clearRect(0, 0, canvas.width, canvas.height)漏掉,残留像素 + 新缩放 = 更糊的混合效果
更稳妥的做法是导出前手动还原为单位缩放:ctx.setTransform(1, 0, 0, 1, 0, 0),再 toDataURL(),之后再恢复高 DPI 缩放。
最易被忽略的点:不要依赖 window.onresize。Flex/Grid/折叠侧边栏等现代布局下,父容器变小但窗口没 resize,ResizeObserver 才是监听真实变化的唯一可靠方式。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











