
Canvas导出图片模糊的根本原因
Canvas画布默认按设备像素比(window.devicePixelRatio)渲染,但canvas.width和canvas.height设置的是CSS像素尺寸。当高DPI屏幕(如Mac Retina、安卓旗舰机)上显示时,浏览器会自动缩放绘制内容,导致实际绘制分辨率不足,导出toDataURL()或toBlob()后图片发虚。
关键不是“画得不够多”,而是“画布物理像素没撑够”。比如你设了canvas.width = 400,在2x屏上它实际只有200个物理像素宽,却要填满400px的CSS宽度——必然插值模糊。
修复模糊:必须同时调整canvas的width/height和CSS样式
这不是只改一个地方的事,必须同步处理两处:
- 用
window.devicePixelRatio放大canvas.width和canvas.height(物理像素尺寸)
- 用CSS把canvas的
width和height设回设计尺寸(CSS像素),维持布局不变
- 重置
ctx.scale(),否则绘图坐标会错乱
window.devicePixelRatio放大canvas.width和canvas.height(物理像素尺寸)width和height设回设计尺寸(CSS像素),维持布局不变ctx.scale(),否则绘图坐标会错乱示例(适配2x屏):
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
<p>// 设计尺寸(CSS像素)
const width = 400;
const height = 300;</p><p>// 放大物理尺寸
canvas.width = width <em> dpr;
canvas.height = height </em> dpr;</p><p>// CSS还原为设计尺寸
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';</p><p>// 缩放绘图上下文,让drawRect(0,0,100,100)仍画在正确位置
ctx.scale(dpr, dpr);</p><p>// 现在绘制、导出都清晰
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100);
const dataUrl = canvas.toDataURL('image/png');toDataURL()导出仍是模糊?检查是否漏了scale或重复缩放
常见错误不是没设dpr,而是缩放逻辑混乱:
- 多次调用
ctx.scale(dpr, dpr)(比如在resize监听里反复执行),导致叠加缩放
- 设置了
canvas.width但忘了改canvas.style.width,画布被CSS拉伸覆盖
- 用
getBoundingClientRect()读取尺寸后直接赋给canvas.width,没除以dpr,结果物理像素反而变少
- 导出前没清空画布(
ctx.clearRect()),旧内容残留+新缩放叠加,边缘更糊
ctx.scale(dpr, dpr)(比如在resize监听里反复执行),导致叠加缩放canvas.width但忘了改canvas.style.width,画布被CSS拉伸覆盖getBoundingClientRect()读取尺寸后直接赋给canvas.width,没除以dpr,结果物理像素反而变少ctx.clearRect()),旧内容残留+新缩放叠加,边缘更糊安全做法:每次重设尺寸后,先ctx.setTransform(1, 0, 0, 1, 0, 0)重置变换,再ctx.scale(dpr, dpr)。
移动端Safari和旧版Chrome的兼容性注意点
iOS Safari对devicePixelRatio支持稳定,但部分Android WebView(尤其旧版Crosswalk)可能返回1,即使设备是2x屏;
- 不要硬编码
dpr = 2,始终用window.devicePixelRatio读取
- 某些低端安卓机
dpr可能是1.5或2.5,直接Math.round()会丢精度,建议用Math.ceil()或保留小数(canvas.width = width * dpr允许小数)
- 导出超大图(如宽>4096px)时,iOS Safari可能截断或崩溃,需分块导出或降采样
dpr = 2,始终用window.devicePixelRatio读取dpr可能是1.5或2.5,直接Math.round()会丢精度,建议用Math.ceil()或保留小数(canvas.width = width * dpr允许小数)导出清晰图的关键不在技巧多炫,而在每一步都对齐物理像素和CSS像素——漏掉任何一个环节,前面全白做。
立即学习“前端免费学习笔记(深入)”;











