canvas移动端模糊的根本原因是画布物理尺寸与css显示尺寸未对齐;需用devicepixelratio放大canvas.width/height,再用css缩回原尺寸,并配合ctx.scale(dpr,dpr)、字体/线宽乘dpr、坐标反向缩放及监听dpr变化重绘。

Canvas在高清移动端模糊,核心问题不是画得不够细,而是“画布物理尺寸”和“显示尺寸”没对齐。设备像素比(DPR)大于1时,1个CSS像素对应多个物理像素,若canvas的width/height属性仍按CSS像素设(比如375×667),浏览器只能插值拉伸渲染——结果就是发虚、文字毛边、线条灰蒙蒙。
正确设置canvas物理分辨率与CSS显示尺寸
关键在于分离两个概念:绘图缓冲区大小(DOM属性)和视觉呈现大小(CSS样式)。必须用DPR放大缓冲区,再用CSS缩回原样,让每个CSS像素由足够多的物理像素承载。
- 获取当前DPR:
const dpr = window.devicePixelRatio || 1;推荐用Math.floor(dpr)避免小数导致渲染异常 - 先读取canvas的CSS尺寸(如
getComputedStyle(canvas).width),转为数值后乘以dpr,赋给canvas.width和canvas.height - 用CSS固定显示尺寸,例如
canvas.style.width = "375px"; canvas.style.height = "667px",确保视觉比例不变 - 重设后调用
ctx.clearRect(0, 0, canvas.width, canvas.height)清空旧缓冲区,防止残留图像干扰
绘制时保持逻辑坐标一致
改完宽高后,所有绘图操作仍按CSS像素写——比如画一个32px宽的按钮,就直接fillRect(x, y, 32, 32),不要手动乘dpr。但需配合ctx.scale(dpr, dpr)让坐标系自动映射到高分屏物理像素。
-
ctx.scale(dpr, dpr)应在每次clear之后、绘制之前调用,且最好封装进渲染主循环 - 文字要清晰:需同步调整
ctx.font,例如原为"14px sans-serif",应改为`${14 * dpr}px sans-serif` - 线宽同理:
ctx.lineWidth = 1 * dpr才能保证1物理像素粗细;否则默认1 CSS像素会变粗或发虚 - 鼠标/触摸点坐标需反向缩放:把
event.clientX / dpr传给isPointInPath等判断方法
响应DPR变化与窗口缩放
DPR并非一成不变——用户切换显示器、系统缩放、甚至iOS Safari地址栏收起都可能触发变化。仅监听resize不够可靠,应优先监听媒体查询变更。
- 用
window.matchMedia(`screen and (min-resolution: ${dpr}dppx)`)创建媒体查询对象 - 对其
change事件添加监听,一旦匹配条件变动,就重新执行canvas尺寸重置+重绘流程 - 重绘前可先用
canvas.toDataURL()暂存当前画面,避免闪烁;复杂场景建议用离屏canvas做缓冲 - 加简单防抖(如300ms间隔),防止频繁触发造成卡顿
兼顾性能与兼容性的取舍建议
高DPR带来清晰度,也带来内存和计算压力。DPR=3时,375×667 CSS尺寸的canvas缓冲区达1125×2001像素,内存占用是DPR=1时的9倍。
- 图标、图表、编辑器画布等关键内容值得全量适配;背景粒子、装饰性动画可降级为
dpr = 1 - 低端Android设备若出现OOM或掉帧,可限制最大DPR(如
Math.min(dpr, 2)) - viewport必须设为
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">,否则initial-scale≠1会导致CSS像素基准错乱 - 避免对canvas父容器使用
transform: scale()或zoom,这类全局缩放会二次干扰渲染管线
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











