Canvas在Retina或高DPR设备上模糊的根本原因是未按设备像素比缩放绘图分辨率,需将width/height设为CSS尺寸×dpr,用CSS控制显示大小,并调用ctx.scale(dpr,dpr)保持逻辑像素坐标。

Canvas在Retina或高DPR设备上模糊,不是Canvas本身有Bug,而是你没告诉它“按物理像素画”。核心就一条:canvas.width 和 canvas.height 必须等于 CSS显示尺寸 × window.devicePixelRatio,再用 ctx.scale(dpr, dpr) 把坐标系拉回逻辑像素单位。
为什么直接设 style.width/height 会模糊
很多人以为给 canvas 加个 style="width:300px;height:150px" 就完事了——错。这只会让浏览器把默认的 300×150 物理像素图像强行压缩进 300×150 CSS 像素区域。在 DPR=2 的设备上,实际是把 300×150 的图拉伸到 600×300 物理像素空间再缩回一半,必然插值模糊。
关键点:
-
canvas.width/canvas.height是绘图分辨率(物理像素数),决定图像原始清晰度 -
style.width/style.height是显示尺寸(CSS像素),决定它在页面上占多大位置 - 两者不一致时,浏览器自动做缩放,且无法关闭
正确初始化高DPR Canvas的步骤
别硬编码 dpr,也别只靠 window.devicePixelRatio ——有些旧版 Safari 或 WebView 不支持,得兜底。同时要监听变化,因为用户可能缩放页面或外接显示器。
立即学习“前端免费学习笔记(深入)”;
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
推荐写法:
function setupHiDPICanvas(canvas, cssWidth, cssHeight) {
const dpr = window.devicePixelRatio || 1;
const bsr = canvas.getContext('2d').backingStorePixelRatio || 1;
const effectiveDPR = dpr / bsr; // 更准的物理像素比
<p>canvas.width = cssWidth <em> effectiveDPR;
canvas.height = cssHeight </em> effectiveDPR;
canvas.style.width = cssWidth + 'px';
canvas.style.height = cssHeight + 'px';</p><p>const ctx = canvas.getContext('2d');
ctx.setTransform(effectiveDPR, 0, 0, effectiveDPR, 0, 0);
return ctx;
}</p><p>// 使用
const canvas = document.getElementById('my-canvas');
const ctx = setupHiDPICanvas(canvas, 600, 400);
ctx.fillRect(10, 10, 100, 50); // 坐标仍按 CSS 像素写,不用乘 dpr
文字和线条仍然发虚?检查坐标是否对齐像素
即使 DPI 适配正确,fillText 或 strokeRect 画出来还是毛边,大概率是坐标含小数。Canvas 对 1px 线条、小字号文字极度敏感:x=10.5 会让浏览器启用亚像素渲染,混合相邻像素颜色,结果就是灰边。
解决办法很简单:
- 绘制前手动取整:
ctx.fillText('Hi', Math.round(x), Math.round(y)) - 画 1px 线条时,确保起始点是整数,且
lineWidth = 1(别设 1.2) - 避免用
transform或scale后再画小元素——浮点误差会累积 - 必要时关抗锯齿:
ctx.imageSmoothingEnabled = false(仅对drawImage有效)
resize 或设备切换后Canvas变糊怎么办
resize 事件不能信。窗口宽度变了,DPR 可能完全没变;反过来,用户从笔记本切到 Retina 外接屏,DPR 变了但窗口尺寸可能没动。真正该监听的是媒体查询变化:
const dpr = window.devicePixelRatio || 1;
const mediaQuery = window.matchMedia(`screen and (min-resolution: ${dpr}dppx)`);
<p>function handleDPRChange() {
setupHiDPICanvas(canvas, 600, 400);
redraw(); // 重绘内容,别只清空
}</p><p>mediaQuery.addEventListener('change', handleDPRChange);
// 初始也要调一次
handleDPRChange();
注意:重设 width/height 会清空画布,所以重绘逻辑必须可触发。别指望靠 toDataURL() 截图再贴回去——那只是把模糊图像再放大一遍。


















