Canvas模糊的根本原因是绘制未对齐物理像素,核心解法是:一、按DPR动态设置canvas宽高为CSS尺寸×DPR并用ctx.scale缩放;二、所有坐标和尺寸取整;三、禁用imageSmoothingEnabled、优化textRendering;四、避免CSS transform缩放,优先用ctx.scale;五、离屏Canvas预渲染后整数倍drawImage输出。

Canvas图形模糊,往往不是画得不够精细,而是“画在了错误的像素位置上”。核心问题在于抗锯齿机制与设备物理像素不匹配——浏览器试图平滑边缘,结果反而抹掉了本该锐利的细节。解决关键不是关掉抗锯齿,而是让绘制坐标、画布尺寸和设备像素严格对齐。
设备像素比(DPR)必须主动适配
高分辨率屏(如iPhone Retina、Windows HiDPI)的1个CSS像素对应多个物理像素(DPR=2/3)。若canvas宽高只设为CSS尺寸(如300×150),实际渲染时会被浏览器拉伸插值,必然模糊。
- 获取真实DPR:
const dpr = window.devicePixelRatio || 1 - 设置物理画布尺寸:
canvas.width = canvas.clientWidth * dpr;,canvas.height = canvas.clientHeight * dpr; - 用CSS保持显示尺寸:
canvas.style.width = canvas.clientWidth + 'px'; - 缩放绘图上下文:
ctx.scale(dpr, dpr);,确保后续所有坐标按逻辑像素书写
坐标必须对齐整数像素
哪怕DPR适配正确,只要绘图坐标含小数(如x=10.5),浏览器就会触发亚像素渲染:混合相邻像素颜色来“模拟”中间位置,结果就是边缘发虚、线条变粗或偏移。
- 矩形、线条、文字起始点都应取整:
ctx.fillRect(Math.round(x), Math.round(y), w, h) - 避免浮点运算累积误差,尤其在循环绘制或动画中,建议用
Math.floor或Math.trunc而非Math.round(后者可能四舍五入到非预期值) - 文字渲染时,配合
ctx.textAlign = 'left'和ctx.textBaseline = 'top',再确保y为整数,可显著提升清晰度
图像缩放与抗锯齿需分场景控制
drawImage缩放图片、ctx.scale变换、CSS transform缩放canvas本身,都会激活插值算法。是否模糊,取决于源图分辨率与目标尺寸的匹配程度。
- 加载高清图源:图标/素材按最高DPR提供(如DPR=3时用3×尺寸图),再用
drawImage等比例绘制 - 关闭位图缩放插值(仅适用于已知不会缩放的图):
ctx.imageSmoothingEnabled = false; - 避免CSS
transform: scale()缩放canvas元素——它会触发二次重采样,优先用ctx.scale()并在DPR适配后操作
字体与细线渲染要特别处理
1px线条、12px文字在高DPR下极易模糊,因为浏览器默认抗锯齿会“柔化”这些极细元素。
- 1px线建议用
lineWidth = dpr并确保坐标对齐(如从(0.5, 0)开始画,在DPR=2时实际占2物理像素宽且居中) - 小字号文字优先使用系统字体(如
'-apple-system, system-ui'),禁用ctx.font = '12px sans-serif'这类固定像素声明 - 必要时开启清晰字体渲染:
ctx.textRendering = 'geometricPrecision';(Chrome/Firefox支持)











