第 10 / 11 章

阅读进度子系统

Zustand 存什么、顶栏阻尼进度条、ArticleShell、侧栏折线目录,以及系列进度键与 ReadingChip / 横向章节条。

⏱ 2 分钟769 字

散篇与系列章节共用同一套进度基础设施。差别集中在进度键和几块系列专用 UI。

进度键怎么定§

内容ArticleShell / history 的键
散篇文章路径名,如 001-mdx-code-blocks
系列章节series/{小册}/{章节},如 series/inkstack-source/02-architecture-and-dirs

键由 lib/series.ts 在建索引时写入 chapter.progressKey,章节页原样传给 ArticleShell。两边共用 Zustand 的 history 字典,但不互相覆盖。

store.ts:三份状态,只持久化一份§

history: Record<string, number>;  // 路径键 → 0~1,写入 localStorage
fraction: number;                 // 当前页滚动比例,不持久化
activeHeading: string | null;     // 当前节标题 id,不持久化

persist 配置里:

  • 存储键:inkstack-reading-v1
  • partialize: (s) => ({ history: s.history })——只保存历史进度,避免把瞬时的 fraction 写盘造成抖动或错误恢复

setFraction / setProgress 都做了量化与现值判断:变化太小就返回旧 state,减少无意义的重渲染。

progress-bar.tsx:为什么不用 React 每帧 setState§

滚动时用 requestAnimationFrame 循环:

  • 目标进度 = scrollY / (scrollHeight - innerHeight)
  • 当前显示值向目标做 * 0.14 的阻尼逼近
  • 直接改 fillRef 的 transform: scaleX(...),避免每帧触发 React 协调
  • 仅当显示值变化超过约 0.0024 时,才 useReading.getState().setFraction,供侧栏百分比等使用

散篇页与系列章节页都挂同一个 ProgressBar。

article-shell.tsx:盯标题 + 存进度 + 继续阅读§

  1. IntersectionObserver:观察 article 内带 id 的 h2/h3/h4,rootMargin 设成上边留页眉、下边留 70%,用「当前视口里最靠上的可见标题」作为 activeHeading
  2. 进度快照:约每 900ms 以及 beforeunload 时,把滚动比例写入 history[slug]
  3. 继续阅读:若本地已有进度且大于约 3%,延迟显示芯片;点击则 scrollTo 到对应比例

系列章节只要把 slug 换成 progressKey,上述逻辑无需分支。

路径:components/reading/article-shell.tsx。

reading-path.tsx:本章标题目录§

大屏右侧(散篇)或系列三栏的右栏,都用 ReadingPathRail;小屏用 ReadingPathFab。

要点回顾:

  1. 按标题深度错开「轨道」,换道用三次贝塞尔
  2. 描边与圆点在约 300ms 内插值
  3. 路由切换时短暂钉住顶部项,避免旧进度串高亮

系列页右栏与散篇右栏是同一组件;左栏「本册目录」才是系列专用(SeriesChapterRail)。

系列与列表上的进度展示§

reading-chip.tsx§

统一的「已读 xx%」芯片(variant="card" | "chapter")。挂在:

  • 散篇卡片(components/posts/post-card.tsx)
  • 小册首页目录(SeriesToc)

按 progressKey 读 history,用 mounted 门闩避免水合不一致。

series-chapter-strip-client.tsx§

窄屏横向章节条。只依赖 @/lib/series-path(不碰 fs)。挂载或换章后,把当前章芯片 scrollIntoView({ inline: "center" })。

home-reading-strip.tsx§

首页「继续阅读」同时扫描散篇 slug 与系列 progressKey,取进度最高的一条;系列章会带上小册名作为上下文。

产品动机与动效取舍的更细讨论,见散篇《阅读进度》专题。下一章收束:站点壳、样式,以及改功能时的文件索引(含系列全表)。