Overlay与HTML叠加
GL canvas 不参与 CSS 布局,也没有可访问树。输入框、工具条、协作光标必须用 DOM overlay,定位矩阵与绘制 / 命中同一套相机。不要在 GL 里自研文本排版和 IME。
1. 世界点 → overlay 像素
export function placeOverlay(
el: HTMLElement,
canvas: HTMLCanvasElement,
worldToCss: (x: number, y: number) => { x: number; y: number },
worldX: number,
worldY: number,
): void {
const p = worldToCss(worldX, worldY);
el.style.position = "absolute";
el.style.left = `${p.x}px`;
el.style.top = `${p.y}px`;
}
worldToCss = 世界 → NDC → CSS 盒,必须与 指针到 NDC 互逆。overlay 放在与 canvas 同一 position: relative 容器 里,坐标相对容器,不要再加 getBoundingClientRect().left(除非 overlay 挂在 body)。
缩放:世界物体跟着相机变;正在输入的 textarea 不要 transform: scale,否则系统候选框错位。只更新 left/top,字号用 CSS 像素。
2. 事件所有权
| 区域 | 谁收指针 |
|---|---|
| 画布空白、轨道球、框选 | canvas,touch-action: none |
| 叠加按钮、输入 | DOM;拖场景经过它们时 overlay pointer-events: none |
| 编辑中的 IME 框 | 始终可点,不要 capture 到 canvas |
setPointerCapture 打在 canvas 上做轨道球。不要用 movementX 累加当相机增量——浏览器缩放、指针锁定下会漂。用前后两帧的 eventToNdc 差。
3. 和 2D / SVG 叠层
同一元素不能既 webgl2 又 2d。生产:
- 两张 canvas 叠:底 GL 场景,顶 2D 画笔迹 / 量尺;命中按 z 决定落到哪张。
- DOM 在上:热区、标签、表单。
不要 texImage2D(canvas2d) 每帧把整张 UI 烤进 GL,除非你在做必须进着色器的 HUD。见 与 2D 叠层。
4. 失败形态
| 症状 | 原因 |
|---|---|
| 标签漂在物体旁边 | overlay 用 CSS 像素,绘制用 drawing buffer 像素 |
| 拖到按钮上轨道球中断 | overlay 没在拖时 pointer-events: none |
| 输入框候选框飞走 | textarea 跟了相机 scale |
| 点标签点到了 GL 物体 | 标签未设命中,事件落到下面 canvas |
权威资料
核对日期:2026-08-26