Canvas 模糊因 width/height 与 style.width/height 不匹配导致缩放插值;应先用 getBoundingClientRect() 获取 CSS 尺寸,再乘以 devicePixelRatio 设置 canvas.width/height,显式设置 style.width/height 为 CSS 像素值,并调用 ctx.scale(dpr, dpr)。

为什么width/height属性和style.width不一致会模糊
Canvas 的 width 和 height 属性控制的是**实际绘制缓冲区的物理像素数**,而 style.width 和 style.height 控制的是它在页面中占用的**CSS 像素尺寸**。当两者数值不等(比如 canvas.width = 600 但 style.width = "300px"),浏览器就必须把 600×600 的图像“压进”300×300 的显示区域——这本质是缩放,且默认用双线性插值,结果就是边缘发虚、1px 线变粗、文字糊成一片。
如何同步 CSS 尺寸与属性尺寸(含 DPR)
关键不是“让两者相等”,而是让 width/height 属性值 = style.width/style.height × window.devicePixelRatio。步骤必须严格按顺序执行:
- 先读取
canvas.getBoundingClientRect()获取当前 CSS 占用尺寸(受 flex/grid/缩放等影响,比clientWidth更可靠) - 用该尺寸 ×
devicePixelRatio设置canvas.width和canvas.height - 再显式设置
canvas.style.width和canvas.style.height为原始 CSS 尺寸(单位必须是px) - 最后调用
ctx.scale(dpr, dpr),否则所有绘图坐标会错位
漏掉任意一步都会导致模糊或错位。例如只改 width/height 但不设 style,Canvas 可能被父容器拉伸;只设 style 但不 scale,图形会缩小四分之一(DPR=2 时)。
getBoundingClientRect() 比 clientWidth 更值得信赖
clientWidth 返回的是元素内容区宽度,忽略 border、transform、滚动条,也不反映 CSS 缩放(transform: scale(0.8) 下它不变)。而 getBoundingClientRect() 返回的是渲染后的真实屏幕坐标,包含所有布局、缩放、滚动偏移的影响,是唯一能准确反映“用户看到多大”的 API。
立即学习“前端免费学习笔记(深入)”;
常见错误写法:canvas.width = canvas.clientWidth * dpr;
正确写法:
const rect = canvas.getBoundingClientRect();<br>canvas.width = rect.width * dpr;<br>canvas.height = rect.height * dpr;
resize 监听里必须重新计算 DPR 和 rect
DPR 在设备旋转(如 iPad 横竖切换)、系统缩放变更(Windows 125% 缩放)、甚至某些浏览器 DevTools 切换设备模拟时会动态变化。不能只在初始化时读一次 devicePixelRatio。
- 监听
window.matchMedia("(resolution)").addEventListener("change", ...)(部分浏览器支持) - 更通用的做法:在
resize或orientationchange回调中,重新调用完整适配逻辑 - 避免高频触发:用
requestAnimationFrame节流,或setTimeout+ 清除前序定时器
另外,如果 Canvas 被嵌套在 transform: scale(0.75) 的容器里,getBoundingClientRect() 已经包含了这个缩放效果,无需额外处理——这点容易被忽略,误加二次缩放反而加剧模糊。


















