移动端canvas模糊源于物理像素不足,需按dpr缩放canvas缓冲区并用ctx.scale(dpr,dpr),同步调整字体、线宽等样式,响应dpr变化并避免拉伸与双重缩放。

移动端高分屏下 Canvas 绘图模糊,不是画得不够细,而是画布的物理像素数量没跟上屏幕真实分辨率。核心是让每个 CSS 像素由整数个物理像素承载,避免浏览器插值拉伸。
正确设置 canvas 物理尺寸与显示尺寸
Canvas 的 width/height 属性(决定缓冲区大小)和 CSS 的 width/height 样式(决定页面占位)必须分离处理:
- 用
getBoundingClientRect()获取 canvas 当前 CSS 尺寸(推荐,不受缩放或 transform 干扰) - 设
canvas.width = rect.width * dpr、canvas.height = rect.height * dpr - 再用 CSS 固定显示尺寸:
canvas.style.width = rect.width + "px"、canvas.style.height = rect.height + "px" - 重设后务必调用
ctx.clearRect(0, 0, canvas.width, canvas.height)清空旧缓冲区
保持绘图逻辑不变:scale 是关键
只放大宽高会让所有坐标“变小”,比如 fillRect(10,10,20,20) 实际只占不到 10 物理像素——必须补上缩放:
- 每次清空后、绘制前执行
ctx.scale(dpr, dpr) - 文字要清晰:原
ctx.font = "14px sans-serif"→ 改为ctx.font = (14 * dpr) + "px sans-serif" - 线宽同理:
ctx.lineWidth = 1 * dpr才能保证 1 物理像素粗细 - 鼠标/触摸点判断时,需反向缩放:
isPointInPath(x / dpr, y / dpr)
响应设备变化,不能只靠 resize
DPR 可能在切换屏幕、系统缩放、iOS 地址栏收起时动态变化,仅监听 resize 不可靠:
- 用
window.matchMedia(`screen and (min-resolution: ${dpr}dppx)`)创建媒体查询对象 - 监听其
change事件,在回调中重新计算尺寸、清空、重设 scale、重绘 - 加简单防抖(如 200ms),防止连续触发卡顿
- 复杂场景可先用
canvas.toDataURL()暂存画面,减少闪烁;静态内容建议用离屏 canvas 预渲染
避开几个典型错误
很多模糊问题其实来自误操作:
- ❌ 只改
style.width/style.height却不改width/height属性——等于强制拉伸低分图 - ❌ 调用
scale(dpr, dpr)后,仍用物理像素坐标绘图(如传fillRect(0,0,2,2))——应始终按 CSS 逻辑像素写 - ❌ 在
scale()之后又用CSS transform: scale()缩放 canvas——双重缩放破坏像素对齐 - ❌ 忽略字体、线宽、阴影等样式未同步乘以 dpr,导致细节发虚











