跳到主要内容

指针事件与elementFromPoint

SVG 有两套「点到了谁」:DOM 命中(走渲染树和 pointer-events)和 几何命中isPointInFill / isPointInStroke)。它们经常不一致。编辑器要先决定用哪套,不要混着写。

1. DOM 命中

export function domHit(
clientX: number,
clientY: number,
root: SVGSVGElement,
): SVGGraphicsElement | undefined {
const node = document.elementFromPoint(clientX, clientY);
if (!(node instanceof SVGGraphicsElement)) return undefined;
if (!root.contains(node)) return undefined;
return node;
}

elementFromPointclient 坐标,已经包含滚动、CSS transform、视口缩放。它尊重:

  • pointer-events
  • display / visibilityhidden 默认不命中)
  • 裁掉的像素(clip / 部分 mask)
  • 文档序(上面的兄弟先命中)

elementsFromPoint 返回叠层列表,用来做「点穿」或多选探测。不要自己遍历全树再 getBoundingClientRect

2. 几何命中:点必须在局部用户空间

SVGGeometryElement.isPointInFill(point) / isPointInStroke(point) 接受 DOMPointInit,点在 该元素局部用户空间,不是 client。

export function geometryHit(
el: SVGGeometryElement,
clientX: number,
clientY: number,
): { fill: boolean; stroke: boolean } {
const ctm = el.getScreenCTM();
if (!ctm) {
return { fill: false, stroke: false };
}
const local = new DOMPoint(clientX, clientY).matrixTransform(ctm.inverse());
return {
fill: el.isPointInFill(local),
stroke: el.isPointInStroke(local),
};
}

clientX 直接丢进去,旋转/缩放节点上永远偏。换算细节见 getScreenCTM 与坐标变换

几何命中 不看 pointer-events,也不看 fill="none" 是否画出来——isPointInFill 测的是填充规则围出的区域。fill: none 的闭合 path,DOM 默认点不中(visiblePainted),几何 fill 仍为 true。

3. pointer-events 值(生产常用)

行为
visiblePainted(默认)只命中实际画出的填/描;fill=nonestroke=none 不中
fill / stroke / all按几何,哪怕对应 paint 是 none
visibleFill / visibleStroke还要求 visibility 为 visible
bounding-boxSVG 特有,整盒都中,含透明空隙
none自己不中,事件穿过

热区、细线、空心框:显式 pointer-events: fill 或叠透明 rect。不要把默认值当「点哪里算哪里」。

4. opacity: 0 仍命中

看不见 ≠ 点不中。opacity: 0 的全屏 rect 会吃掉下面所有点击。隐藏占位用 visibility: hiddenpointer-events: nonedisplay: none 移出渲染树,getScreenCTM 可能为 null。完整表见 绘制顺序与层叠

fill-opacity: 0fill 不是 none 时,默认 visiblePainted 仍命中填充区。要「全透明穿透」必须 pointer-events: nonefill: none

5. 适用 / 不适用

适用:图标按钮、流程图节点——DOM 命中足够。
不适用:笔刷命中描边中心线、要忽略组级 pointer-events 的精确几何——走 isPointInFill,且从上到下自己遍历 pick 列表。

6. 失败形态

症状原因
空心框只能点到 1px 描边默认 visiblePainted + fill: none
旋转后 isPointInFill 全偏传入了 client 坐标
点空白却选中组或透明 rectopacity: 0 / fill-opacity: 0
elementFromPoint 拿到 HTML overlay没限制在 svg.contains
被裁切的角仍几何命中用了 isPointInFill 而产品要的是可见像素

权威资料

核对日期:2026-08-26