调整 HTML Canvas 元素大小

最后更新: 2024-08-12 15:47:28 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

HTML canvas 有两种大小:

  1. width 和 height 属性定义其绘图缓冲区。
  2. CSS 宽度和高度定义其显示框。

混淆这两种大小会产生模糊的输出、拉伸的坐标或过高的内存使用量。

绘图缓冲区和显示大小 #

Canvas 属性包含无单位的像素数量:

html
<canvas width="800" height="400"></canvas>

默认绘图缓冲区为 300×150 像素。CSS 可以将该缓冲区以其他大小显示:

css
canvas {
  inline-size: 100%;
  block-size: auto;
}

当 CSS 框和绘图缓冲区的宽高比不同时,浏览器会拉伸位图。编写 width="800px" 在意图上是无效的,因为该属性是整数,而不是 CSS 长度。

更改 canvas.width 或 canvas.height 会清除位图并重置渲染上下文状态,包括变换、剪切区域、样式和当前路径。

匹配设备像素比 #

高密度显示屏会将一个 CSS 像素映射到多个设备像素。根据渲染后的 CSS 框设置后备存储:

js
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 大小可能因视口调整大小、容器查询、字体加载或周围内容而改变。应观察元素,而不是只监听窗口调整大小:

js
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 像素为单位传入。当绘图代码使用后备存储坐标时,应将其映射到绘图缓冲区:

js
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 上阅读或评论(发表评论需要登录)