Canvas 在高 DPI 屏模糊的根源是 width/height 未匹配物理像素,需用 devicePixelRatio 动态设置 canvas 尺寸并调用 ctx.scale(),同时确保图像、字体、坐标等均按 dpr 缩放。

Canvas 在高 DPI 屏上模糊,不是 Canvas 本身有问题,而是你没让它的 width 和 height 属性匹配物理像素 —— 直接用 CSS 尺寸赋值或忽略 window.devicePixelRatio,就等于让浏览器强行拉伸位图。
获取并验证 devicePixelRatio 是否生效
别假设 window.devicePixelRatio 总是 2 或 3;它可能为 1(普通屏)、1.25(部分 Windows 缩放)、1.5(某些安卓平板)、2(iPhone 6–13)、3(iPhone 14 Pro)甚至更高。关键是要读取真实值,并确认它被用于后续计算:
- 在页面加载后立即打印:
console.log(window.devicePixelRatio),注意是否为undefined(旧 IE 不支持,需降级处理) - 某些安卓 WebView 或 Electron 环境下,
window.devicePixelRatio可能始终返回 1,即使设备是 2x 屏 —— 此时需结合screen.availWidth / window.innerWidth估算,但不推荐作为主逻辑 - 监听
resize事件不足以捕获 dpr 变化;真正可靠的是监听matchMedia('(resolution)')或使用window.addEventListener('DOMContentLoaded', ...)+ 首次渲染后重查
重设 canvas.width/height 与 style 尺寸必须同步
只改 canvas.style.width 是 CSS 缩放,不增加绘制精度;只改 canvas.width 而不调 ctx.scale(),会导致所有坐标按物理像素写,代码难以维护。正确顺序不可颠倒:
- 先用
canvas.getBoundingClientRect()拿到当前 CSS 占位尺寸(避免依赖硬编码) - 用
dpr放大canvas.width和canvas.height:例如占位宽 400px、dpr=2 →canvas.width = 800 - 再设置
canvas.style.width和canvas.style.height回原始 CSS 值(如"400px"),否则 canvas 会撑开布局 - 最后调用
ctx.scale(dpr, dpr),使fillRect(0, 0, 100, 100)仍画出 100×100 的逻辑区域,而非 100×100 物理像素的小方块
drawImage 和文字渲染仍会模糊?检查资源与坐标
就算 canvas 本身清晰了,drawImage() 贴的图、fillText() 写的字照样发虚 —— 因为它们没跟着 dpr 缩放:
- 图像资源:如果
img.src指向的是 1x 图(比如icon.png),在 2x 屏上用ctx.drawImage(img, 0, 0)绘制,等同于把 100×100 的图拉伸到 200×200 物理像素 —— 必须换 2x 图(icon@2x.png)或服务端带?dpr=2参数 - 文字坐标:
ctx.font = "14px Arial"在 dpr=2 下实际只占 14 物理像素高,远低于屏幕最小可分辨单位;应设为ctx.font = "28px Arial"(即${14 * dpr}px Arial),同时确保fillText(x, y)的y是整数(非 10.5),否则触发亚像素抗锯齿 -
drawImage()尺寸参数:若传入目标宽高(第 5–6 参数),务必除以 dpr,例如ctx.drawImage(img, 0, 0, img.width, img.height, x, y, img.width/dpr, img.height/dpr),否则目标区域仍按逻辑像素算,导致图像被错误缩放
容易被忽略的边界情况
最常翻车的地方不在主流程,而在这些细节:
- Canvas 容器有
transform: scale()或zoom:这会干扰getBoundingClientRect()返回值,建议避免对 canvas 父容器做 CSS 缩放 - 动态调整 canvas 大小后未清空缓存:调用
ctx.setTransform(1, 0, 0, 1, 0, 0)重置变换矩阵,否则残留的scale会叠加 - 使用
toDataURL()导出图片时:导出的是当前canvas.width/height对应的位图,所以必须确保此时已按 dpr 设置好分辨率,否则截图仍是模糊的 - 字体 fallback:
ctx.font中指定的字体在某些设备上不可用,浏览器会 fallback 到默认字体,其渲染行为不受 dpr 控制 —— 应预加载或限制字体栈


















