不能只靠 resizeobserver自动修正高分屏canvas比例,必须手动结合window.devicepixelratio计算渲染缩放,并用contentrect控制布局尺寸;否则在retina或windows缩放下canvas会模糊或错位。

直接结论:不能只靠 ResizeObserver 自动修正高分屏 Canvas 比例,必须手动结合 window.devicePixelRatio 计算渲染上下文缩放,并用 contentRect.width/height 控制布局尺寸 —— 否则在 Retina/MacBook Pro 或 Windows 缩放 125%/150% 下,Canvas 会模糊或错位。
为什么 contentRect.width 不等于 Canvas 渲染所需像素数
Canvas 的清晰度取决于两个独立维度:CSS 布局尺寸(用户可见大小)和 backing store 像素数(实际绘制分辨率)。ResizeObserver 的 entry.contentRect.width 返回的是 CSS 像素值(即 layout width),它不随设备像素比变化;但 canvas.getContext('2d') 默认以 1:1 映射 CSS 像素到设备像素,导致高分屏下内容被拉伸、发虚。
- 例如:容器 CSS 宽 400px,在 MacBook Pro 上
window.devicePixelRatio === 2,真实 backing store 需要 800 像素宽才能清晰 -
entry.contentRect.width仍是 400,不会自动翻倍 - 若只按
contentRect设置canvas.width,就漏掉了设备像素补偿
如何同步更新 canvas.width / canvas.height 和 style
必须在 ResizeObserver 回调中同时设置 Canvas 的 CSS 尺寸(控制布局)和 backing store 尺寸(控制清晰度),且二者比例要严格一致。
- 先读取
entry.contentRect.width和entry.contentRect.height - 乘以
window.devicePixelRatio得到目标 backing store 像素数 - 用
Math.round()截断小数,避免 subpixel 渲染抖动 - 显式设置
canvas.style.width和canvas.style.height为 CSS 像素值(即原始contentRect值) - 设置
canvas.width和canvas.height为 device-pixel 值 - 最后调用
ctx.scale(dpr, dpr)确保绘图坐标系对齐
示例关键片段:
const dpr = window.devicePixelRatio || 1;
resizeObserver.observe(container);
const resizeHandler = (entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(height * dpr);
const ctx = canvas.getContext('2d');
ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // 重置并应用缩放
}
};
容易忽略的兼容性与销毁陷阱
现代浏览器基本支持 ResizeObserver,但仍有几个硬伤点必须处理:
- Safari 13.1+ 才支持
contentBoxSize,而旧版仅返回contentRect—— 别依赖entry.contentBoxSize[0].inlineSize,它在 Safari 13.0 及更早版本是 undefined - React/Vue 组件卸载时未调用
resizeObserver.unobserve(container)或resizeObserver.disconnect(),会导致内存泄漏和重复回调 - 某些 Android WebView(如 UC 内核)仍不支持
ResizeObserver,需 fallback 到getBoundingClientRect()+requestAnimationFrame轮询(但仅限兜底,性能差) -
window.devicePixelRatio在页面缩放时可能动态变化(如用户按 Ctrl+/-),但ResizeObserver不会触发 —— 若需响应缩放变化,得额外监听visualViewport的resize事件
真正麻烦的不是写几行监听代码,而是把「容器 CSS 尺寸」「设备像素比」「Canvas backing store」「绘图坐标系缩放」这四层关系始终对齐。少一步,Canvas 就糊一次。










