canvas在retina屏模糊的根源是物理像素与css显示尺寸未对齐;需按devicepixelratio放大canvas宽高、用css缩回显示尺寸,并调用ctx.scale(dpr,dpr)保持逻辑坐标一致,同时适配字体、线宽及响应dpr变化。

Canvas 在 Retina 屏上模糊,不是画得不够细,而是画布“物理像素”和“显示尺寸”没对齐。核心解决思路是:让 canvas 的绘图缓冲区覆盖足够多的物理像素,再用 CSS 压回设计大小,最后用 ctx.scale() 保持绘图逻辑不变。
正确设置 canvas 物理尺寸与 CSS 显示尺寸
浏览器把 canvas 当作一张位图来渲染。如果 CSS 设为 300×200px,但 canvas.width 和 canvas.height 还是 300×200,那在 DPR=2 的设备上,这张图就要被拉伸到 600×400 物理像素——必然模糊。
- 先读取当前设备像素比:
const dpr = window.devicePixelRatio || 1 - 获取 canvas 的目标显示宽高(推荐用
getBoundingClientRect(),避免依赖固定值) - 设 canvas DOM 属性:
canvas.width = Math.floor(rect.width * dpr),canvas.height = Math.floor(rect.height * dpr) - 用 CSS 控制视觉大小:
canvas.style.width = rect.width + "px",canvas.style.height = rect.height + "px"
绘制前必须缩放绘图上下文
改完宽高后,坐标系也变“密”了。不调用 scale,画一个 10×10 的矩形会变成 10/dpr × 10/dpr,几乎看不见。
- 每次清空画布后、开始新帧前,执行:
ctx.scale(dpr, dpr) - 之后所有绘图操作(
fillRect、arc、drawImage)仍按 CSS 像素写,比如按钮宽 48px 就直接传 48 - 文字和线宽需同步适配:
ctx.font = "14px sans-serif"→ctx.font = `${14 * dpr}px sans-serif`;ctx.lineWidth = 1→ctx.lineWidth = 1 * dpr
响应设备变化,避免静态假设
DPR 不是一成不变的。插拔外接显示器、系统缩放切换(如 Windows 从 100% 切到 125%)、甚至 iOS Safari 地址栏收起,都可能触发变化。只监听 resize 是不够的。
- 用
window.matchMedia(`screen and (min-resolution: ${dpr}dppx)`)创建媒体查询对象 - 监听其
change事件,变化时重新计算尺寸、清空、重设scale、重绘 - 重绘前可先用
canvas.toDataURL()暂存画面,减少闪烁;复杂场景建议用离屏 canvas 缓冲 - 加简单防抖(如 100–300ms),避免频繁重设影响性能
避开常见坑点
很多模糊问题其实来自误操作,而非机制本身。
- ❌ 只改
style.width/style.height却不改width/height属性——等于强制拉伸低分图 - ❌
scale(dpr, dpr)后仍用物理像素坐标绘图(例如画 1px 线却传fillRect(0,0,2,2))——应始终按 CSS 逻辑像素写 - ❌ 鼠标/触摸坐标没反向缩放:
event.clientX / dpr才是对应画布内的逻辑坐标 - ❌ 对 canvas 父容器用了
transform: scale()或zoom——会二次干扰渲染管线
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











