canvas的width/height属性设置绘图缓冲区分辨率,CSS宽高仅控制显示区域;未适配devicePixelRatio会导致模糊、导出失真等问题,需按容器尺寸×DPR动态设置缓冲区并重绘。

canvas 的 width/height 属性和 CSS 宽高不是一回事
直接在 HTML 里写 <canvas width="400" height="300">,和用 CSS 写 style="width: 400px; height: 300px",效果完全不同。前者是设置 canvas 的「绘图缓冲区分辨率」,后者只是拉伸显示区域。浏览器会把低分辨率画布强行撑大,导致模糊、锯齿、线条变粗。
常见错误现象:ctx.lineWidth = 1 画出来像 2 像素宽;文字边缘发虚;getImageData 读到的像素数和预期不符。
- 绘图前务必先设
canvas.width和canvas.height,别只靠 CSS - 如果要响应式缩放,先读取容器尺寸,再按设备像素比(
window.devicePixelRatio)计算真实缓冲尺寸 - 设完
width/height后,canvas 内容会被清空,需重绘
用 ctx.scale() 做逻辑缩放时,坐标系变了
ctx.scale(2, 2) 不只是“画面变大”,它会让所有后续绘制的坐标、长度、字体大小都按比例放大——包括 moveTo 的 x/y、fillRect 的宽高、甚至 font 字号。但鼠标事件坐标、getBoundingClientRect() 返回值仍是 CSS 像素,不自动适配。
使用场景:做矢量图表、地图缩放、或统一控制 UI 元素精细度,避免每个数值都手动乘缩放系数。
立即学习“前端免费学习笔记(深入)”;
- 缩放后,鼠标点击位置要反向换算:
realX = (clientX - rect.left) / scale -
ctx.scale()是累积的,多次调用会叠加,记得用ctx.save()/ctx.restore()隔离作用域 - 字体缩放后可能失真,建议用
ctx.font = "normal 12px sans-serif"固定字号,靠缩放整体布局而非单改字体
高清屏(Retina)下 canvas 模糊的根本原因
问题不在 canvas 本身,而在没对齐物理像素。比如在 window.devicePixelRatio === 2 的设备上,CSS 设为 width: 200px,实际需要 400 物理像素宽的缓冲区,否则浏览器双线性插值拉伸,必然模糊。
性能影响:缓冲区过大(如 4K 屏上设 8000×6000)会显著增加内存和 GPU 压力,尤其移动端容易卡顿或崩溃。
- 获取真实缓冲尺寸公式:
const dpr = window.devicePixelRatio || 1;,然后canvas.width = container.clientWidth * dpr; - 设完后必须同步调用
canvas.style.width = container.clientWidth + "px",保持 CSS 尺寸不变 - 不要无脑设
dpr,部分安卓 WebView 对高 DPR 支持差,可限制最大值为 2
Canvas 缩放后导出图片变糊?toDataURL() 和 toBlob() 用的是缓冲区原尺寸
canvas.toDataURL("image/png") 输出的图片分辨率,永远等于 canvas.width × canvas.height,和 CSS 缩放、ctx.scale() 无关。所以如果你用 CSS 把 200×150 的 canvas 拉到 400×300 显示,导出仍是 200×150 的图。
容易被忽略的地方:想导出“当前看到的清晰图”,得先临时把 canvas 缓冲区放大到目标尺寸,重绘内容,再导出——不能只靠缩放显示。
- 导出前备份原始尺寸:
const w = canvas.width, h = canvas.height; - 临时放大:
canvas.width = targetWidth; canvas.height = targetHeight;,再重绘(或用drawImage把原内容缩放贴过去) - 导出完记得恢复:
canvas.width = w; canvas.height = h;,否则后续绘制错乱
事情说清了就结束。缩放本质是三件事打架:缓冲区分辨率、CSS 渲染尺寸、绘图坐标系。哪一环没对齐,模糊、错位、导出失真就跟着来。











