Sticky Reveal
吸頂圖文對照揭示:左側文字隨捲動流動,右側視覺吸附固定並交叉切換。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
24
<StickyReveal />
安裝
npx shadcn@latest add https://webberui.com/r/sticky-reveal.json或在 components.json 設定 registries 後,改用 @webberui/sticky-reveal 安裝。
安裝依賴後,從 registry JSON(/r/sticky-reveal.json 的 files[0].content)複製 sticky-reveal.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge使用
以 StickyRevealItem 宣告每一段對照,media 放右側吸頂視覺、children 放左側文字:
import { StickyReveal, StickyRevealItem } from "@/components/ui/sticky-reveal";
<StickyReveal>
<StickyRevealItem media={<img src="/design.jpg" alt="設計" className="h-full w-full object-cover" />}>
<h3 className="text-2xl font-semibold">設計</h3>
<p className="mt-2 text-neutral-600">從語意化 token 出發,全站節奏一致。</p>
</StickyRevealItem>
<StickyRevealItem media={<img src="/ship.jpg" alt="發佈" className="h-full w-full object-cover" />}>
<h3 className="text-2xl font-semibold">發佈</h3>
<p className="mt-2 text-neutral-600">一鍵安裝,內建 reduced-motion。</p>
</StickyRevealItem>
</StickyReveal>若內容位於巢狀捲動容器中,將該容器 ref 透過 container 傳入(頁面級捲動則可省略):
const scrollerRef = React.useRef<HTMLDivElement>(null);
<div ref={scrollerRef} className="h-[80vh] overflow-y-auto">
<StickyReveal container={scrollerRef} stickyTop={16}>
{/* ...StickyRevealItem */}
</StickyReveal>
</div>Props
StickyReveal
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | ReactNode | — | 放入一組 StickyRevealItem |
mediaSide | "left" | "right" | "right" | 視覺區位置(左文右圖或左圖右文) |
stickyTop | number | 24 | 吸頂視覺框距容器頂端的偏移(px) |
activeIndex | number | — | 受控模式:由外部指定作用段落索引 |
onActiveChange | (index: number) => void | — | 作用段落改變時觸發 |
container | RefObject<HTMLElement | null> | — | 巢狀捲動容器 ref;省略則以視窗為準 |
showProgress | boolean | true | 顯示可點擊的段落進度圓點 |
stepClassName | string | "min-h-[70vh]" | 每段文字容器的 class(撐開捲動距離) |
mediaClassName | string | — | 吸頂視覺框的 class(覆寫比例、圓角等) |
className | string | — | 根容器的 class |
StickyRevealItem
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
media | ReactNode | — | 對照顯示的視覺內容(圖片或圖形) |
children | ReactNode | — | 隨捲動流動的文字內容 |
className | string | — | 附加到該段文字容器的 class |
細節
- 採用
IntersectionObserver監看每段文字,以捲動容器「中央細帶」判定作用段落,單一時刻只有一段作用。 - 所有
media疊放在同一個吸頂框內,作用者淡入、非作用者淡出並帶輕微縮放與模糊,形成交叉揭示。 - 受控/非受控雙模式:未傳
activeIndex時內部自管狀態;傳入則由你掌控,捲動仍會透過onActiveChange回報。 StickyRevealItem僅作宣告,實際佈局由StickyReveal讀取其 props 統一渲染,以便集中管理吸頂與交叉切換。- 可用
useStickyReveal()在子樹中讀取active/total/isStatic,打造自訂進度指示。
可及性
- 尊重
prefers-reduced-motion:停用交叉淡入的過渡與模糊、進度圓點過渡,點擊定位改為即時跳轉;版面(吸頂)維持不變。 - 非作用中的視覺層標記
aria-hidden,避免輔助科技重複朗讀。 - 進度圓點為原生
<button>,可鍵盤聚焦與觸發,作用中者帶aria-current,點擊平滑捲回該段。 - 附帶
aria-live="polite"區域即時播報「目前第 N 段,共 M 段」。