扇形锚点导航测试页

这一页专门用来试 TocFanNav:下面的每个 ## 都会出现在右侧的扇形容器里,点击即可跳转。 (测试用文件,不需要了直接删掉即可。)

一 · 准备工作

先确认右侧导轨里同时出现了两个东西:滚动指示条和扇形锚点导航。

扇形导航用的是 FanVirtualList 的默认 driver="self",也就是它自己带一条滚动轨道; 滚动指示条则继续跟随窗口滚动。两者互不干扰,这一点在下面几节里会反复验证。

二 · 几何关系

扇形把「轨道距离」换算成圆心角:theta = (progress - index) * itemHeight / radius。

圆心落在锚点右侧 radius 处,所以 theta = 0 的那一条正好压在锚点上, 其余条目沿弧向两端张开,横向会轻微外凸。半径越小,弧越弯、条目倾角越大; 半径越大,越接近一条直列。

三 · 前位与淡出

距离前位越远,条目越淡、倾角越大,超出 maxAngle 后就不再渲染。

maxAngle 同时决定虚拟窗口的 overscan:窗口必须比可见角度范围更宽, 否则弧上的条目会被提前卸载,滚动时会出现"边缘条目闪一下"的现象。

四 · 点击跳转

点击任意一条,正文会平滑滚动到对应标题,同时把这条转到扇形的前位。

跳转做两件事:页面 scrollIntoView({ behavior: 'smooth' }) 到 heading, 轨道 scrollTo({ behavior: 'smooth' }) 到 index * itemHeight。 和 window 模式不同,这里不需要任何窗口进度的参与 —— 前位完全由轨道决定。

五 · 手写滚动

扇形自己是一条可滚动的轨道,把鼠标放在它上面滚动只会动扇形,不会动页面。

这也意味着:用户可以用滚轮浏览锚点列表,而正文保持不动; 真要读某一节,再点一下跳过去。

六 · 末尾留白

容器末尾补了一段留白(end-padding="container"),否则最后一条永远到不了前位。

原因很直接:轨道的 scrollTop 上限是「内容总高 − 容器高」, 不加留白的话,最后「容器高 / itemHeight」条根本没有机会滚到锚点位置。

七 · 键盘

当前只做了点击;如果要加方向键,在 TocFanNav 里接 reader.jumpBy(±1) 就行。

reader 里已经把 jumpBy 写好了,它按 activeIndex 前后移动并复用同一条跳转逻辑。

八 · 与文档列表的差别

RightTabWithNav 用 driver="window" 跟随页面滚动;这里用默认的 driver="self"。

还有一个区别是数据源:文档列表来自 content store 的 summaries, 这里来自 reader store 的 toc(当前文档的 page.body.toc)。

九 · 结束

滚到最后一条,它会稳稳停在扇形的前位上。