进阶用法:用 anchor() 在两个元素之间画连接线
本系列其他页的浮层都只引用一个锚(anchor() 的基础用法见 anchor() 页)。但
anchor() 里可以写显式锚名,于是同一个元素的四条边能分别锚到不同的锚——把它「撑」在两个元素之间,只画其中两条边,就得到一根会自适应的连接线。灵感来自
Roland Franke 的这篇 frontend story:给评论和它的回复之间画一根 thread 线,零 JS、零额外测量。
核心思路:一个绝对定位元素,inset-block-start: anchor(--A end) 把顶边钉到 A 的底,inset-block-end: anchor(--B center) 把底边钉到 B 的中线……四条 inset 各引用 anchor,元素就被拉伸成「跨越 A 与 B」的盒子。再用
border-inline-start + border-block-end + 圆角只画左下两边 → 肘形串线。
1 · 动手:改缩进 / 间距 / 文案,串线自己重画
下面 comment → reply 之间那根蓝线,是纯 CSS 撑出来的。拖滑块改回复的缩进与垂直间距、换长文案,线会自动重新连好:
为什么这是「画线」而不是「摆浮层」?浮层只关心自己放哪(一个锚);连接线关心自己从哪到哪(两个锚)。anchor() 能在同一元素的不同 inset 上引用不同锚名,这正好表达「一段区间」。换内容、换字号、换
RTL,线都跟着重算——因为它读的全是实时布局值,没有一个写死的坐标。
2 · 多个 thread 并存:anchor-scope 关住同名锚
真实页面里会有一长列评论,每个组件都复用同样的类名(--comment / --reply)。若不隔离,第二个 thread 的线可能去连第一个 thread 的锚。给每个 thread 加 anchor-scope: --comment, --reply(recipes 页的配方 4),名字就只在本组件内解析,互不干扰。下面两个独立 thread 各连各的:
注意锚的可用性顺序。anchor() 只能引用在 DOM 里先于查询元素、且在其 anchor-scope 内的锚。所以连接线元素要放在两个气泡之后(本例放 thread 末尾)。这也是它能稳定连上 comment 与 reply 的前提。
3 · 再进一步:把直角肘形换成贝塞尔曲线 + 箭头
肘形线靠 border 画,只能折直角。想要平滑曲线 + 箭头就得用 SVG——但撑盒子仍由 anchor() 完成:让一个 <svg> 用和肘形线完全相同的四边 anchor() 跨越 comment→reply,再把这个盒子的真实像素尺寸喂给 SVG 的
viewBox(于是 1:1 等比、不被拉伸)。曲线和箭头的坐标用一点三角函数算:描边只画到箭头底边、三角补出尖端,箭头就总能严丝合缝地盖住曲线尾:
箭头为什么总能精确覆盖曲线尾端?这是个纯几何问题,不依赖目测:取曲线终点 E 与单位切线 u,沿切线从 E 回退箭头长 A 得到底边中点 B——描边只画到 B,箭头三角再从 B(底边,沿法线 ±半宽)补到
E(尖端)。线尾正好落在三角最宽的底边处,所以无论盒子被 anchor 撑成什么比例,箭头都覆盖线尾、不会突出。前提是 SVG 1:1 不被拉伸:把 anchor() 撑出的真实像素喂给 viewBox(ResizeObserver
一变就重画),切线角度才不会因非等比缩放而失真。定位归 anchor,描点归这几行三角函数。