高 DPI 和视网膜屏幕上的清晰画布渲染
为什么画布图形在 Retina 和高 DPI 屏幕上看起来模糊,以及如何使用 devicePixelRatio 修复它,包括调整大小处理和性能限制。
核心要点
当画布的像素大小与其 CSS 大小匹配时,画布在高密度屏幕上看起来很模糊,因为每个画布像素都拉伸到多个物理像素上。通过将画布宽度和高度设置为 CSS 大小乘以 window.devicePixelRatio 来修复此问题,保持 CSS 大小不变,并按相同比例缩放绘图上下文。更新它以调整大小和缩放。
为什么画布模糊
MDN 将 devicePixelRatio 描述为当前显示器上物理像素与 CSS 像素的比率:在经典 96 DPI 屏幕上为 1,在典型 HiDPI 或 Retina 屏幕上为 2。画布有自己的像素缓冲区。如果该缓冲区为 300 x 150 像素,但在比例为 2 的屏幕上以 300 x 150 CSS 像素显示,则浏览器会将其拉伸超过 600 x 300 物理像素,这看起来很柔和。
修复
MDN 的推荐方法是使用 devicePixelRatio 来添加额外的像素密度:将画布缓冲区设置为 CSS 大小乘以比率,保持 CSS 大小相同,并缩放 2D 上下文,以便您的绘图代码可以继续使用 CSS 像素。请参阅下面的代码。
处理调整大小和缩放
当窗口移动到另一个监视器或用户缩放页面时,比率会发生变化。在调整大小时重新计算,并侦听分辨率媒体查询的更改,以便您可以以新的密度重新绘制。 canvas 元素上的 ResizeObserver 是保持尺寸与布局同步的可靠方法。
性能限制
比率加倍会使要绘制的像素增加四倍。在非常大的画布或低端设备上,将比率限制为 2,并测量帧时间。绘制到画布的动画运行时(例如 Lottie 的画布渲染器、Rive 和 ExodeUI)可以为您处理这种缩放,但同样的权衡也适用。
生产实施片段
function setupCanvas(canvas) {
const ctx = canvas.getContext('2d');
function resize() {
const ratio = Math.min(window.devicePixelRatio || 1, 2); // cap for performance
const { width, height } = canvas.getBoundingClientRect(); // CSS size
canvas.width = Math.round(width * ratio); // backing store in device pixels
canvas.height = Math.round(height * ratio);
ctx.setTransform(ratio, 0, 0, ratio, 0, 0); // draw in CSS pixels
draw(ctx, width, height);
}
new ResizeObserver(resize).observe(canvas);
resize();
}
function draw(ctx, width, height) {
ctx.clearRect(0, 0, width, height);
ctx.lineWidth = 1;
ctx.strokeRect(0.5, 0.5, width - 1, height - 1); // crisp 1px border
}要记住的关键 Principle
- devicePixelRatio = 每 CSS 像素的物理像素(在典型的 Retina 屏幕上为 2)
- 设置canvas.width/height为CSS尺寸×比例;保持CSS尺寸
- 缩放上下文,以便绘图代码使用 CSS 像素
- 重新计算调整大小、监视更改和缩放
- 限制大画布上的比例以提高性能
常见问题解答
来源
- Window: devicePixelRatio property — MDN 网络文档