canvas在高分屏下模糊的根本原因是dpr升高导致css尺寸与实际像素不匹配,需通过matchmedia监听resolution变化或resize+轮询dpr来动态重置,并在resetcanvas中同步设置width/height和ctx.scale(dpr,dpr)。

Canvas 在高分屏(如 Retina)下模糊,根本原因不是“屏幕变高清了”,而是设备像素比(dpr)升高后,画布的 CSS 尺寸和实际像素尺寸不匹配——浏览器拉伸了低分辨率像素。媒体查询本身**不能直接监听 dpr 变化**(它不触发重排/重绘事件),但可以配合 window.devicePixelRatio 检测和 matchMedia 监听系统级显示缩放变更,实现动态重置。
用 matchMedia 监听系统缩放变化
现代浏览器支持通过 matchMedia 查询 (resolution) 或 (prefers-reduced-motion) 等媒体特性。虽然没有标准的 (dpr: 2) 语法,但可监听 screen 媒体类型的 resolution 变化(部分 Chromium 和 Safari 支持):
- 监听
resolution变更(需注意兼容性):const media = window.matchMedia('(resolution: 2dppx)'); - 注册回调,在匹配状态改变时重设 canvas:
media.addEventListener('change', () => resetCanvas()); - 首次加载也需主动调用:
resetCanvas();
更可靠:监听 resize + 定期检查 dpr
因为 matchMedia('(resolution)') 并非全平台稳定支持,生产环境推荐组合策略:
- 在
window.addEventListener('resize', ...)中检查当前window.devicePixelRatio是否变化 - 搭配
requestAnimationFrame做轻量轮询(仅当检测到 dpr 变动才重置):let lastDpr = window.devicePixelRatio;function checkDpr() {if (window.devicePixelRatio !== lastDpr) {resetCanvas();lastDpr = window.devicePixelRatio;}requestAnimationFrame(checkDpr);} - 该方式覆盖了窗口缩放、系统 DPI 切换、甚至 macOS “显示器缩放模式”切换等场景
resetCanvas 函数必须做三件事
重置不只是改 width/height,必须同步缩放坐标系,否则绘图会错位或变形:
- 读取当前
dpr = window.devicePixelRatio || 1 - 根据 canvas 的
getBoundingClientRect()计算 CSS 像素尺寸,再乘以dpr设置真实像素尺寸:canvas.width = rect.width * dpr;canvas.height = rect.height * dpr; - 对 2D 上下文执行
ctx.scale(dpr, dpr)—— 这一步让所有后续绘图坐标自动适配,无需修改业务逻辑 - 重置后建议清空一次画布:
ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, canvas.width, canvas.height);(避免旧缩放残留)
注意:避免重复重置与性能陷阱
频繁重设 canvas 尺寸会清空像素、重建缓冲区,影响动画性能:
- 只在
dpr实际变化时执行重置(缓存上一次值对比) - 不要在每帧
requestAnimationFrame里调用resetCanvas - 如果 canvas 是固定尺寸(如 800×600 不随窗口缩放),可跳过 resize 监听,仅在初始化时按当前 dpr 设置一次
- 对全屏 canvas,建议用
position: fixed+inset: 0替代width: 100vw,防止因滚动条导致getBoundingClientRect波动
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











