把 DOM 坐标画成 SVG:目录连线
HTML / CSS 能给单个盒子描边、加圆角,却画不出「一条平滑曲线把两个相距很远、嵌套层级不同的标题连起来」——因为这需要一个坐标系。SVG 的每个图形本就是坐标驱动的,而坐标可以来自 getBoundingClientRect 量出的真实 DOM 布局。于是把前面几页的机制组合起来,就得到文档目录里那种连线:量 DOM → 实时生成 path → 用 scroll 位置驱动 clip-path 把静态连线「擦」成进度高亮。全程动画只改 clip-path,不触发 layout。本页是 fumadocs 那类目录连线(见
SVG art II)的拆解,用到的优先级与几何属性可回看 presentation attribute、坐标系可回看 viewBox。
第一步:为什么连线必须交给 SVG
目录里每一项是一个普通 DOM 节点,它们在页面上的位置由文字、行距、嵌套缩进决定,运行时才知道。要在这些节点之间画线,等于在「任意两点之间」绘图——这正是 SVG 坐标系的本职。做法是把一段覆盖在目录文字之下的 <svg> 铺满目录区,它不写 viewBox,于是 1 user unit = 1 CSS
px,getBoundingClientRect 量出的像素坐标可直接当作 path 坐标。
第二步:量 DOM,实时生成 path
逐项取 getBoundingClientRect,减去 SVG 容器自身的 rect,得到每项中心点的 (x, y);x 由嵌套深度决定(越深越往右),y 是项的垂直中心。相邻两点用一段 cubic Bézier(控制点放在两点的纵向中点)连成 S
形。下面拖行距滑块改变目录的 DOM 布局,连线会实时重算——SVG 完全是 DOM 的派生物,不是手画的:
第三步:scroll 位置驱动 clip-path
把同一条连线叠两份:底层 track 浅色常驻,顶层 progress 深色被一个矩形 clip-path: inset(0 0 N% 0) 从下往上裁掉。滚动左侧文章时,按 scrollTop 比例算出 N,连线就像进度条一样被「擦」出来,同时高亮当前小节对应的目录项。关键在于:每帧只改 clip-path 这一个 CSS 值,连线本身(path 的 d)一动不动。
为什么用 clip-path 而不是 mask-image 或每帧重设 d: clip-path 的矩形变化只发生在 compositor 阶段,不触发 layout 与 paint;改 mask-image 的尺寸 / 位置则可能引发重排。每帧重设
stroke-dashoffset 也能做「自我绘制」(见 fill & stroke),但那需要精确的路径总长;clip-path 用一个矩形就能裁出任意复杂连线,与 path 形状无关,更省心。