指针事件与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;
}
elementFromPoint 用 client 坐标,已经包含滚动、CSS transform、视口缩放。它尊重:
pointer-eventsdisplay/visibility(hidden默认不命中)- 裁掉的像素(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=none 且 stroke=none 不中 |
fill / stroke / all | 按几何,哪怕对应 paint 是 none |
visibleFill / visibleStroke | 还要求 visibility 为 visible |
bounding-box | SVG 特有,整盒都中,含透明空隙 |
none | 自己不中,事件穿过 |
热区、细线、空心框:显式 pointer-events: fill 或叠透明 rect。不要把默认值当「点哪里算哪里」。
4. opacity: 0 仍命中
看不见 ≠ 点不中。opacity: 0 的全屏 rect 会吃掉下面所有点击。隐藏占位用 visibility: hidden 或 pointer-events: none。display: none 移出渲染树,getScreenCTM 可能为 null。完整表见 绘制顺序与层叠。
fill-opacity: 0 但 fill 不是 none 时,默认 visiblePainted 仍命中填充区。要「全透明穿透」必须 pointer-events: none 或 fill: none。
5. 适用 / 不适用
适用:图标按钮、流程图节点——DOM 命中足够。
不适用:笔刷命中描边中心线、要忽略组级 pointer-events 的精确几何——走 isPointInFill,且从上到下自己遍历 pick 列表。
6. 失败形态
| 症状 | 原因 |
|---|---|
| 空心框只能点到 1px 描边 | 默认 visiblePainted + fill: none |
旋转后 isPointInFill 全偏 | 传入了 client 坐标 |
| 点空白却选中 | 组或透明 rect 的 opacity: 0 / fill-opacity: 0 |
elementFromPoint 拿到 HTML overlay | 没限制在 svg.contains |
| 被裁切的角仍几何命中 | 用了 isPointInFill 而产品要的是可见像素 |
权威资料
核对日期:2026-08-26