canvas在retina屏发虚是因为未适配devicepixelratio,需同步设置width/height属性为物理像素值并缩放绘图坐标,否则仅css缩放会导致插值模糊。

Canvas 在 Retina 屏上发虚是因为没处理 devicePixelRatio
浏览器默认用 1 个 CSS 像素对应 1 个物理像素,但 Retina 屏(如 MacBook、iPhone)的 devicePixelRatio 是 2 或 3——意味着 1 个 CSS 像素要铺开成 4 或 9 个物理像素。Canvas 的 width/height 属性是按物理像素设置的,而 CSS 样式(比如 style="width: 300px; height: 200px;")控制的是 CSS 像素。两者不匹配,浏览器只能靠插值拉伸,结果就是模糊。
必须同时改 canvas 元素的 width 和 height 属性,不能只改 CSS
只写 canvas.style.width = "300px" 不起作用:它只缩放显示区域,画布缓冲区还是原始大小,绘图内容被拉伸。真正要改的是 canvas 元素的 DOM 属性 width 和 height(不是 CSS),让缓冲区物理像素数匹配设备密度。
实操建议:
- 读取
window.devicePixelRatio,向下取整或四舍五入(通常用Math.floor避免小数导致渲染异常) - 先保存原始 CSS 尺寸(比如
getComputedStyle(canvas).width),再换算成物理像素值 - 赋值前清空 canvas(
ctx.clearRect(0, 0, canvas.width, canvas.height)),否则旧缓冲区残留会干扰重绘 - 别在
resize事件里频繁重设——加防抖,或者只在devicePixelRatio变化时响应(监听matchMedia的"(resolution: ...dppx)"更可靠)
ctx.scale(dpr, dpr) 是补救手段,不是替代方案
如果你已经用 CSS 控制了 canvas 大小,又不想动 width/height 属性,可以用 ctx.scale(dpr, dpr) 把所有绘图坐标放大。但它只是“骗”绘图 API,实际缓冲区仍不足,文字、线条抗锯齿效果差,且 getImageData、toDataURL 输出的仍是低分辩率图像。
立即学习“前端免费学习笔记(深入)”;
常见错误现象:
- 文字边缘毛刺、小图标出现灰边——
scale后没调用ctx.font重新设字号,或没对lineWidth手动乘dpr - 鼠标坐标错位——没把
clientX/clientY除以dpr再传给isPointInPath - 动画卡顿——每帧都做
scale+translate组合变换,不如直接提升缓冲区分辨率来得干净
兼容性与性能注意点:高 dpr 下内存和绘制成本陡增
devicePixelRatio 为 3 时,同样 300×200 CSS 尺寸的 canvas,缓冲区变成 900×600 物理像素,内存占用 ×9,clearRect 和 drawImage 开销也显著上升。低端 Android 设备可能直接 OOM 或掉帧。
使用场景建议:
- 图标、图表、游戏主画布——值得适配;纯装饰性 canvas(比如背景粒子)可降级为
dpr = 1 - 服务端生成图片(
node-canvas)不涉及devicePixelRatio,别套用这套逻辑 - iOS Safari 15.4+ 支持
canvas.getContext("2d", { alpha: false })提升性能,但和 dpr 无关,别混淆
最常被忽略的一点:canvas 元素插入 DOM 后才可读取 getComputedStyle,动态创建时别在 new Canvas() 后立刻计算尺寸。











