Canvas在Retina屏模糊的根本原因是画布未按设备像素比(DPR)设置足够物理像素,需三步解决:放大canvas.width/height、CSS缩回显示尺寸、调用ctx.scale(dpr,dpr)对齐坐标系。

Canvas 在 Retina 屏上模糊,核心不是“画得不够细”,而是“画布没按物理像素密度准备足够多的绘制点”。关键动作就三步:放大画布内在尺寸、压回 CSS 显示大小、再用 ctx.scale() 把坐标系对齐回来。
正确设置 canvas 的 width/height 和 style 尺寸
浏览器渲染 Canvas 时,width/height 属性决定绘图表面的物理像素数,而 style.width/style.height 只控制它在页面上占多大视觉空间。两者不匹配就会拉伸模糊。
- 先获取设备像素比:
const dpr = window.devicePixelRatio || 1 - 假设你希望 Canvas 视觉宽高是 400×300px,那就设:
canvas.width = 400 * dprcanvas.height = 300 * dpr - 再用 CSS 把它“缩回”来:
canvas.style.width = '400px'canvas.style.height = '300px'
必须调用 ctx.scale(dpr, dpr)
只改尺寸还不够。此时绘图上下文仍按原始逻辑像素工作,比如 fillRect(0,0,1,1) 会画出一个 1×1 物理像素的小方块——在 DPR=2 的屏幕上,它只占 0.5×0.5 CSS 像素,几乎看不见。
- 调用
ctx.scale(dpr, dpr)后,所有坐标单位自动映射到 CSS 像素:画fillRect(0,0,1,1)就真能填满 1 个视觉像素 - 这个缩放要放在每次清空画布后、正式绘图前执行,不能漏
- 不需要手动把坐标乘以 dpr,否则会画错位置
响应容器变化,不只是窗口大小
现代布局中,侧边栏收起、Flex 容器重排都可能改变 Canvas 父容器尺寸,但 window.onresize 不一定触发。靠监听容器更可靠。
- 用
ResizeObserver监听父容器:new ResizeObserver(() => setupHiDPI(canvas)).observe(container) - 重设时先保存当前图像(如
canvas.toDataURL()),再清空、重设width/height、重设style、重调scale()、最后重绘 - 避免用
transform: scale()或zoom缩放整个 Canvas 元素,这会二次干扰渲染管线
验证是否真正生效
别只靠肉眼判断“好像清楚了”。可做两个简单验证:
- 画一条 1px 水平线:
ctx.beginPath(); ctx.moveTo(0, 50); ctx.lineTo(400, 50); ctx.stroke(),截图放大看边缘是否锐利、无灰边 - 用
ctx.getImageData(0, 50, 1, 1)读取线上某点像素,确认返回的是不透明纯色值,而非半透明混合色


















