跳到主要内容

Overlay与DOM叠加

Canvas 不参与 CSS 布局,也没有可访问树。要把输入框、右键菜单、协作光标标牌叠上去,必须用 同一套 world ↔ view 函数 定位 DOM,而不是再写一套坐标。

1. 定位

export function placeOverlay(
el: HTMLElement,
canvas: HTMLCanvasElement,
camera: Camera,
worldX: number,
worldY: number,
): void {
const view = worldToView(camera, worldX, worldY);
const rect = canvas.getBoundingClientRect();
el.style.position = "absolute";
el.style.left = `${rect.left + window.scrollX + view.x}px`;
el.style.top = `${rect.top + window.scrollY + view.y}px`;
el.style.transformOrigin = "0 0";
el.style.transform = `scale(${camera.scale})`;
}

若 overlay 已在与 canvas 同坐标的 wrapping position: relative 容器里,不要加 rect.left,只用 view 坐标。两种布局选一种。

缩放文字:transform: scale(camera.scale) 让 DOM 与世界物体同步;IME 输入框通常 不要 跟着 scale(否则系统候选框错位)。正在编辑的 textarea 固定 CSS 像素,只更新 left/top。

2. 事件所有权

区域谁收指针
画布空白、节点拖拽canvas,touch-action: none
叠加的按钮、输入DOM,canvas 不要 capture 到它们头上
拖节点经过 overlay仍应走 canvas capture,overlay pointer-events: none,编辑中的输入除外

拖拽开始时给 overlay 加 pointer-events: none,结束时去掉。否则拖到标牌上会 pointerout 打断。

3. 文本编辑的标准拆法

双击文字节点 → 在世界盒位置挂 <textarea>,内容来自 scene,canvas 那一行改成不画(或画占位)。Enter/blur 写回 scene,卸 DOM。不要在 canvas 里自己做 caret 闪烁和 IME。

4. 适用 / 不适用

适用:属性气泡、协作头像、临时输入、右键菜单。
不适用:把整页 UI 画进 canvas。按钮、表单、Tab 焦点必须是 DOM。

5. 失败形态

症状原因
缩放后输入框漂一套用 backing 坐标,一套用 CSS
拖到气泡就丢拖overlay 抢走 pointer
中文输入候选框飞textarea 被 scale
滚动页面后 overlay 留在原地用了 client 当 page,没加 scroll

权威资料

核对日期:2026-08-26