HTML canvas 有两种大小:
width和height属性定义其绘图缓冲区。- CSS 宽度和高度定义其显示框。
混淆这两种大小会产生模糊的输出、拉伸的坐标或过高的内存使用量。
绘图缓冲区和显示大小 #
Canvas 属性包含无单位的像素数量:
<canvas width="800" height="400"></canvas>默认绘图缓冲区为 300×150 像素。CSS 可以将该缓冲区以其他大小显示:
canvas {
inline-size: 100%;
block-size: auto;
}当 CSS 框和绘图缓冲区的宽高比不同时,浏览器会拉伸位图。编写 width="800px" 在意图上是无效的,因为该属性是整数,而不是 CSS 长度。
更改 canvas.width 或 canvas.height 会清除位图并重置渲染上下文状态,包括变换、剪切区域、样式和当前路径。
匹配设备像素比 #
高密度显示屏会将一个 CSS 像素映射到多个设备像素。根据渲染后的 CSS 框设置后备存储:
function resizeCanvas(canvas, context) {
const rect = canvas.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
return false;
}
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const width = Math.round(rect.width * dpr);
const height = Math.round(rect.height * dpr);
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
const scaleX = width / rect.width;
const scaleY = height / rect.height;
context.setTransform(scaleX, 0, 0, scaleY, 0, 0);
return true;
}应用变换后,绘图代码可以使用 CSS 像素坐标,同时后备存储保留高密度细节。当大型 canvas 会消耗过多内存时,应限制缩放比例:RGBA 缓冲区在实现特定的额外开销之前,每个像素约需要四个字节。
使用 setTransform,而不是重复调用 scale,为每次重绘建立变换。否则,反复乘以当前变换会使 canvas 在每次调整大小通知时不断增大。为任一维度赋值都会重置上下文,即使赋值等于当前值,因此应先进行比较,并在每次实际赋值后重绘。
响应布局变化 #
Canvas 大小可能因视口调整大小、容器查询、字体加载或周围内容而改变。应观察元素,而不是只监听窗口调整大小:
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
const observer = new ResizeObserver(() => {
if (resizeCanvas(canvas, context)) {
render(context);
}
});
observer.observe(canvas);避免因更改绘图缓冲区属性而改变 CSS 大小,从而形成反馈循环。让 CSS 独立控制显示框。
ResizeObserverEntry.devicePixelContentBoxSize 可以提供以设备像素表示的内容框,而无需经过 CSS 像素舍入步骤。在较旧的引擎中并不普遍支持,因此生产代码需要回退到 contentBoxSize 或 getBoundingClientRect()。
坐标映射和输入 #
指针坐标以视口 CSS 像素为单位传入。当绘图代码使用后备存储坐标时,应将其映射到绘图缓冲区:
const rect = canvas.getBoundingClientRect();
const x = (event.clientX - rect.left) * (canvas.width / rect.width);
const y = (event.clientY - rect.top) * (canvas.height / rect.height);CSS 变换可能使显示几何结构比简单缩放更加复杂。对于经过变换的交互式 canvas,应反转适用的 DOMMatrix,而不是使用宽度比。如果绘图使用上一节中的 CSS 像素变换,则减去 rect.left 和 rect.top 已经会产生逻辑绘图坐标;再次应用后备存储比例会使输入被缩放两次。
库和离屏渲染 #
图形库可以在调整渲染器大小时更新 canvas 属性。例如,库可以独立于 CSS 框设置绘图缓冲区。直接赋值属性之前,应先读取库的大小 API,因为外部赋值可能会丢弃渲染器状态。
OffscreenCanvas 具有绘图缓冲区大小,但没有 CSS 框。主线程必须将所需的像素尺寸传递给工作线程。传输图像或已渲染帧时,不要假设工作线程能够检查布局。在 transferControlToOffscreen() 之后,渲染所有权属于离屏 canvas;从原始 DOM canvas 请求另一个渲染上下文是无效的。
评论
在 GitHub 上阅读或评论(发表评论需要登录)