WebberUI

Synced Outline Reader

內容+大綱雙欄閱讀版面:指示脊椎隨閱讀位置連續伸縮滑動,當前章節同步聚焦。

載入預覽⋯

安裝

npx shadcn@latest add https://webberui.com/r/synced-outline-reader.json

安裝依賴後,從 registry JSON(/r/synced-outline-reader.jsonfiles[0].content)複製 synced-outline-reader.tsx 原始碼到你的 components/ui/ 目錄:

npm install motion clsx tailwind-merge

使用

sections 定義大綱清單,內容以 OutlineSection 包裹、id 一一對應:

import {
  SyncedOutlineReader,
  OutlineSection,
} from "@/components/ui/synced-outline-reader";

const sections = [
  { id: "intro", title: "簡介" },
  { id: "usage", title: "使用方式" },
  { id: "faq", title: "常見問題" },
];

<SyncedOutlineReader sections={sections}>
  <OutlineSection id="intro" title="簡介">
    <p>第一章內文……</p>
  </OutlineSection>
  <OutlineSection id="usage" title="使用方式">
    <p>第二章內文……</p>
  </OutlineSection>
  <OutlineSection id="faq" title="常見問題">
    <p>第三章內文……</p>
  </OutlineSection>
</SyncedOutlineReader>

滾動發生在巢狀 overflow 容器內時,把該容器的 ref 傳給 container

const scrollerRef = React.useRef<HTMLDivElement>(null);

<div ref={scrollerRef} className="h-[400px] overflow-y-auto">
  <SyncedOutlineReader sections={sections} container={scrollerRef}>
    {/* ... */}
  </SyncedOutlineReader>
</div>

Props

SyncedOutlineReader

Prop型別預設值說明
sections{ id: string; title: string }[]大綱欄的章節清單,順序需與內容中的 OutlineSection 一致
childrenReactNode直接放入 OutlineSection 子元件
containerRefObject<HTMLElement>滾動發生在巢狀 overflow 容器內時,傳入該容器的 ref
outlineLabelstring"內容大綱"大綱欄 <nav> 的無障礙標籤
classNamestring附加到雙欄外層容器的 class

OutlineSection

Prop型別預設值說明
idstring章節錨點 id,需對應 sections 陣列中的同名項目
titleReactNode章節標題,渲染為 <h2>
childrenReactNode章節內文
classNamestring附加到章節 <section> 的 class

細節

  • 指示脊椎不是傳統 scroll-spy 的跳格切換:以 IntersectionObserverroot 為捲動容器)追蹤各章節的可見比例,換算成大綱項上的覆蓋範圍,再用 useSpring 驅動 topheight,在大綱項之間連續伸縮滑動
  • 當前章節(可見比例最高者)內文 opacity 為 1,其餘章節降至 0.6,閱讀焦點隨滾動同步轉移
  • 大綱項是真正的 <button>,點擊平滑捲動到對應章節,並以 aria-current="location" 標示當前位置
  • 窄螢幕大綱欄整欄隱藏,md 斷點以上才顯示雙欄;內容欄不受影響
  • 使用者系統開啟「減少動態效果」時,脊椎直接跳到目標位置(不經彈簧)、聚焦切換不再過渡、點擊大綱改為即時捲動,版面完全不變

On this page