Scrollytelling Stage
左側 sticky 數據舞台+右側步驟文章的雙欄滾動敘事,舞台圖形狀態、高亮區域與註解標線依步驟索引補間 morph 切換。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
100
16
<ScrollytellingStage />
安裝
npx shadcn@latest add https://webberui.com/r/scrollytelling-stage.json或在 components.json 設定 registries 後,改用 @webberui/scrollytelling-stage 安裝。
安裝依賴後,從 registry JSON(/r/scrollytelling-stage.json 的 files[0].content)複製 scrollytelling-stage.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge使用
每個 ScrollytellingStep 用 scene 描述捲到該步驟時左側舞台應呈現的狀態;捲動時舞台會在相鄰步驟的場景之間補間 morph。
import {
ScrollytellingStage,
ScrollytellingStep,
} from "@/components/ui/scrollytelling-stage";
<ScrollytellingStage>
<ScrollytellingStep title="平穩起步" scene={{ data: [30, 42, 38, 46, 40] }}>
<p>基準線:各週活躍度大致持平。</p>
</ScrollytellingStep>
<ScrollytellingStep
title="週中放量"
scene={{
data: [30, 55, 74, 60, 44],
annotation: { at: 2, label: "週三高峰" },
}}
>
<p>推出新功能後,週三出現單日高峰。</p>
</ScrollytellingStep>
<ScrollytellingStep
title="後段承接"
scene={{
data: [34, 58, 70, 82, 76],
highlight: [3, 4],
annotation: { at: 3, label: "維持在高檔" },
}}
>
<p>高亮區間顯示週四、週五站上新高。</p>
</ScrollytellingStep>
</ScrollytellingStage>;若捲動發生在固定高度的巢狀 overflow-y-auto 容器內,把容器的 ref 傳給 container:
const scrollerRef = React.useRef<HTMLDivElement>(null);
<div ref={scrollerRef} className="h-[300px] overflow-y-auto">
<ScrollytellingStage container={scrollerRef}>
{/* ...steps */}
</ScrollytellingStage>
</div>;需要完全自訂舞台時,改用 renderStage(取代內建資料圖);它會收到目前 activeIndex 與連續步進的 progress(MotionValue,0 至 步數-1):
<ScrollytellingStage
renderStage={({ activeIndex, progress }) => (
<MyCustomStage step={activeIndex} progress={progress} />
)}
>
{/* ...steps */}
</ScrollytellingStage>Props
ScrollytellingStage
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | ReactNode | — | 一組 ScrollytellingStep |
container | RefObject<HTMLElement | null> | — | 巢狀 overflow 捲動容器的 ref;省略則以視窗為捲動容器 |
maxValue | number | 100 | 數值上限,長條高度以此正規化 |
stickyTop | number | 16 | 左側舞台吸頂位置(px) |
renderStage | (state) => ReactNode | — | 自訂舞台渲染,收到 { activeIndex, progress } |
className | string | — | 外層容器 class |
stageClassName | string | — | 左側舞台外框 class;以 text-* 設定強調色 |
ScrollytellingStep
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
scene | StageScene | — | 此步驟對應的舞台狀態(見下) |
title | ReactNode | — | 步驟標題 |
children | ReactNode | — | 步驟正文 |
className | string | — | 步驟外層 class |
StageScene
| 欄位 | 型別 | 說明 |
|---|---|---|
data | number[] | 各長條數值,依 maxValue 正規化為高度 |
highlight | [number, number] | 高亮區間 [起始索引, 結束索引](含端點,可為小數);省略則不顯示 |
annotation | { at: number; label: string } | 註解標線指向第 at 根長條並顯示 label;省略則不顯示 |
細節
- 以
useScroll追蹤右側步驟欄的捲動進度,換算成連續步進值(0 至 步數-1)作為所有補間的唯一驅動來源,因此長條高度、高亮帶與註解標線的變形永遠同步。 - 高亮帶與註解標線使用
currentColor,可透過stageClassName的text-*一次設定整體強調色。 - 註解標線鄰近的長條會漸亮為強調色,形成隨捲動移動的聚光效果。
- 缺少
highlight或annotation的場景,對應圖元會平滑淡出,morph 不會跳動。
可及性
- 使用者系統開啟「減少動態效果」時,舞台改為逐步驟即時切換(不補間、不做過渡),版面與內容完全不變。
- 左側舞台帶
role="img"與描述目前步驟的aria-label;內部 SVG 圖元對輔助科技隱藏。 - 步驟為語意化的有序清單(
<ol>/<li>),作用中步驟標記aria-current="step",並以不透明度與標題強調;鍵盤沿用瀏覽器原生捲動即可依序閱讀。