CSS 与布局 / 🔷 SVG × CSS:一块能被 CSS 管的画布 / 把 DOM 坐标画成 SVG:目录连线 待审核 7 / 9
scroll · DOM→SVG ★

把 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 形,控制点放在两点的纵向中点。

图 2-1 · 可拖行距滑块改变目录的 DOM 布局,连线会实时重算——SVG 完全是 DOM 的派生物,不是手画的。

3 · scroll 位置驱动 clip-path

把同一条连线叠两份:底层 track 浅色常驻,顶层 progress 深色被一个矩形 clip-path: inset(0 0 N% 0) 从下往上裁掉。滚动文章时按 scrollTop 比例算出 N,连线就像进度条一样被擦出来,同时高亮当前小节对应的目录项。关键在于每帧只改 clip-path 这一个 CSS 值,连线本身(path 的 d)一动不动。

图 3-1 · 可滚动左侧文章,看深色进度沿连线自下而上擦出,目录项同步高亮。

建议 ·clip-path 而非 mask-image 或每帧重设 d,理由有两条。其一,clip-path 的矩形变化只发生在 compositor 阶段,不触发 layout 与 paint;改 mask-image 的尺寸或位置则可能引发重排。其二,每帧重设 stroke-dashoffset 也能做自我绘制(见 fill & stroke),但那需要精确的路径总长;clip-path 用一个矩形就能裁出任意复杂连线,与 path 形状无关,更省心。