路径几何与测量
d 是编辑器的 source of truth。测长、描边进度、沿路径摆放,都建立在 规范化后的 SVGGeometryElement 上,而不是对每种基本形状各写一套。
用户空间 = viewBox 用户单位,见 viewport 与 viewBox。
1. getTotalLength / getPointAtLength
export function clamp01(t: number): number {
return Math.min(1, Math.max(0, t));
}
export function pointAtNormalized(
geometry: SVGGeometryElement,
t: number,
): DOMPoint {
const length = geometry.getTotalLength();
if (!(length > 0)) {
throw new Error("path has zero length");
}
return geometry.getPointAtLength(clamp01(t) * length);
}
getTotalLength() 返回 UA 按当前用户空间算出来的长度,不是 pathLength 属性。沿路径动画、箭头摆放,进度乘的是这个实测值。
距离越界时引擎通常夹紧到 [0, length],不要依赖「超出就延长切线」。t 自己夹紧。
适用:描边进度、沿轨迹运动、等距采样。
不适用:把 getPointAtLength 当布尔运算或碰撞检测——它只给中心线采样点,不含线宽。线宽命中用 isPointInStroke,见 指针事件。
2. pathLength:规范化 dash,不替代测长
<path d="M 10 80 Q 95 10 180 80" pathLength="1" stroke-dasharray="0.6 1"
stroke-dashoffset="0.6" fill="none" stroke="currentColor"/>
作者给定的 pathLength 用来标定 stroke-dasharray / stroke-dashoffset / 部分 SMIL 距离,让「60% 进度」与真实像素长度脱钩。进度环、仪表盘用它,避免每帧 getTotalLength()。
不要假设 getTotalLength() === Number(path.getAttribute("pathLength"))。两套数:dash 走 pathLength,几何采样走 getTotalLength。
3. 规范化成 path
三引擎对 rect / circle 是否实现 getTotalLength 仍有历史差。编辑器、箭头、dash 进度,生产先变成 path:
export function circleToPathD(cx: number, cy: number, r: number): string {
const k = 0.5522847498307936 * r;
return [
`M ${cx + r} ${cy}`,
`C ${cx + r} ${cy + k} ${cx + k} ${cy + r} ${cx} ${cy + r}`,
`C ${cx - k} ${cy + r} ${cx - r} ${cy + k} ${cx - r} ${cy}`,
`C ${cx - r} ${cy - k} ${cx - k} ${cy - r} ${cx} ${cy - r}`,
`C ${cx + k} ${cy - r} ${cx + r} ${cy - k} ${cx + r} ${cy}`,
"Z",
].join(" ");
}
export function setPathD(path: SVGPathElement, d: string): void {
path.setAttribute("d", d);
}
静态图标、简单图表继续用基本形状,可读、文件小。一旦要节点编辑、布尔运算、沿边采样,换成 d,不要两套几何并存。
d 的写入同样走 setAttribute。pathSegList 已死,不要找 polyfill 续命。
4. getBBox 默认不含描边
SVG 2:getBBox({ fill = true, stroke = false, markers = false, clipped = false })。默认盒是填充几何,线宽和 marker 不进盒。
export function visualBBox(el: SVGGraphicsElement): DOMRect {
return el.getBBox({ fill: true, stroke: true, markers: true, clipped: false });
}
盒在 元素局部用户空间。元素 display: none、不在渲染树、或空几何时可能抛错或得到全 0。自身 transform 是否计入以浏览器为准——生产把盒四个角经 getScreenCTM 变到需要的空间,不要假设 getBBox 已是屏幕盒。
clipped: true 按裁剪后的可见盒;框选、对齐仍用未裁几何,否则被 clipPath 的节点「缩水」。
5. 失败形态
| 症状 | 原因 |
|---|---|
| 进度环 dash 随缩放乱跳 | 没用 pathLength,每帧用实测长去除不定 |
getPointAtLength 抛错或 NaN | 空 path / 未挂载 / 长度 0 |
| 包围盒比看得见的描边小一圈 | 默认 stroke: false |
| 旋转后框选偏了 | 把局部 getBBox 当屏幕矩形 |
circle 上 getTotalLength 是 0 | 引擎未实现形状测长,没规范化成 path |
权威资料
核对日期:2026-08-26