把 DOM 坐标画成 SVG:目录连线
HTML 与 CSS 能给单个盒子描边、加圆角,却画不出「一条平滑曲线把两个相距很远、嵌套层级不同的标题连起来」——因为这需要一个坐标系。SVG 的每个图形本就是坐标驱动的,而坐标可以来自 getBoundingClientRect 量出的真实 DOM 布局。于是把前面几页的机制组合起来,就得到文档目录里那种连线:量 DOM、实时生成
path、用 scroll 位置驱动 clip-path 把静态连线擦成进度高亮,全程只改这一个 CSS 值,不触发 layout。
本页是 fumadocs 那类目录连线(见 SVG art II)的拆解,用到的优先级与几何属性可回看 presentation attribute,坐标系可回看 viewBox。
1 · 为什么连线必须交给 SVG
目录里每一项是一个普通 DOM 节点,它们在页面上的位置由文字、行距、嵌套缩进决定,运行时才知道。要在这些节点之间画线,等于在任意两点之间绘图,这正是 SVG 坐标系的本职。做法是把一段覆盖在目录文字之下的 <svg> 铺满目录区,它不写 viewBox,于是 1 user unit 等于 1 CSS px,getBoundingClientRect
量出的像素坐标可直接当作 path 坐标。
2 · 量 DOM 与实时生成 path
逐项取 getBoundingClientRect,减去 SVG 容器自身的 rect,得到每项中心点的坐标;横坐标由嵌套深度决定(越深越往右),纵坐标是项的垂直中心。相邻两点用一段 cubic Bézier 连成 S 形,控制点放在两点的纵向中点。
3 · 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 形状无关,更省心。