高清屏canvas模糊需同步设置canvas.width/height、css尺寸和ctx.scale();仅改style.width会导致插值拉伸发虚;正确做法是按devicepixelratio缩放缓冲区并重置上下文变换。

Canvas在高清屏模糊,不是画布“画得不清”,而是浏览器把逻辑像素强行拉伸到更多物理像素上——必须同步改canvas.width/canvas.height、CSS尺寸和ctx.scale(),缺一不可。
为什么直接设style.width会越调越糊
只改canvas.style.width = "300px"却不碰canvas.width,等于让300个物理像素撑满300px显示区域。在2x屏上,这300px实际要占600个物理像素,浏览器只能插值拉伸,结果就是发虚、文字边缘朦胧、细线变粗。
- 典型现象:普通屏正常,一连MacBook或开Windows 125%缩放立刻模糊
- 验证方法:用
ctx.getImageData(0, 0, 1, 1)读1×1区域,若返回4个以上非透明像素,说明已拉伸 - 错误操作:用
transform: scale(2)或zoom: 2套在canvas外层——这是二次干扰,不解决根本问题
三步法设置高清Canvas分辨率
核心是分离“绘图缓冲区大小”(物理像素)和“显示尺寸”(CSS像素),再用ctx.scale()对齐坐标系。
- 先获取真实尺寸:
const rect = canvas.getBoundingClientRect(),别用offsetWidth——它不含CSS缩放影响 - 设物理像素尺寸:
canvas.width = rect.width * dpr、canvas.height = rect.height * dpr - 用CSS压回视觉尺寸:
canvas.style.width = rect.width + "px"、canvas.style.height = rect.height + "px" - 重置并缩放上下文:
ctx.setTransform(1, 0, 0, 1, 0, 0)再ctx.scale(dpr, dpr),避免多次scale叠加
响应式场景下必须监听devicePixelRatio变化
用户切显示器、调系统缩放、甚至Chrome里按Ctrl +放大页面,都可能让window.devicePixelRatio突变——此时仅监听resize不够。
- 推荐监听:
window.matchMedia(`screen and (min-resolution: ${dpr}dppx)`).addEventListener('change', handleDPRChange) - 重设时先保存图像:
const dataUrl = canvas.toDataURL(),再清空、重设尺寸、重绘,否则内容错位 - 降级兜底:
const dpr = window.devicePixelRatio || 1,旧Android WebView可能返回1,但至少不报错
toDataURL()导出还是糊?检查这三个点
导出模糊往往不是DPR没设,而是缩放逻辑混乱导致的坐标偏移或残留绘制。
- 是否在
ctx.scale(dpr, dpr)后,又用物理像素坐标绘图?例如想画100×100逻辑矩形,却写了fillRect(0, 0, 200, 200) - 是否重复执行
ctx.scale()?比如在resize回调里没重置变换,导致scale(2)执行两次变成scale(4) - 导出前是否
ctx.clearRect(0, 0, canvas.width, canvas.height)?旧内容+新缩放叠加,边缘更糊
最易被忽略的是:每次重设尺寸后,ctx的变换矩阵是继承上次状态的,不手动setTransform(1, 0, 0, 1, 0, 0)就直接scale(),等于在未知基础上再缩放——这点在动态缩放或频繁重绘场景里,几乎必踩。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











