View Continuity Grid
卡片網格與詳情頁之間的真實視圖切換,被點卡片的圖片與標題以 View Transitions API 跨頁延續,其餘卡片依距離向外退散、返回時依序歸位。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
2
64
0.5
<ViewContinuityGrid />
安裝
npx shadcn@latest add https://webberui.com/r/view-continuity-grid.json或在 components.json 設定 registries 後,改用 @webberui/view-continuity-grid 安裝。
安裝依賴後,從 registry JSON(/r/view-continuity-grid.json 的 files[0].content)複製 view-continuity-grid.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion lucide-react clsx tailwind-merge使用
傳入 items 陣列即可。點擊卡片會切換到該項目的詳情頁,圖片與標題以 View Transitions API 跨頁變形延續,其餘卡片向外退散;返回時依序歸位。
import {
ViewContinuityGrid,
type ViewContinuityItem,
} from "@/components/ui/view-continuity-grid";
const items: ViewContinuityItem[] = [
{
id: "aurora",
title: "極光平原",
eyebrow: "北緯 69°",
image: "/images/aurora.jpg",
description: "詳情頁的內文…",
},
// …
];
<ViewContinuityGrid items={items} columns={2} />;受控模式
傳入 openId 與 onOpenChange 即可由外部(例如路由狀態)驅動目前展開的項目;null 代表回到網格:
const [openId, setOpenId] = React.useState<string | null>(null);
<ViewContinuityGrid items={items} openId={openId} onOpenChange={setOpenId} />;自訂詳情內容
預設詳情頁渲染 item.description。若要完全掌控詳情頁的排版,傳入 renderDetail:
<ViewContinuityGrid
items={items}
renderDetail={(item) => (
<article className="prose">
<p>{item.description}</p>
<a href={`/posts/${item.id}`}>閱讀全文 →</a>
</article>
)}
/>;Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
items | ViewContinuityItem[] | — | 網格項目資料 |
columns | number | 2 | 網格欄數(1–4) |
openId | string | null | — | 受控模式:目前展開項目的 id(null 為回到網格) |
defaultOpenId | string | null | null | 非受控模式的初始展開 id |
onOpenChange | (id: string | null) => void | — | 展開/返回時觸發 |
spread | number | 64 | 其餘卡片向外退散的基準距離(px) |
duration | number | 0.5 | 單次過場時長(秒) |
imageAspect | string | "4 / 3" | 圖片長寬比(CSS aspect-ratio 值) |
renderDetail | (item: ViewContinuityItem) => ReactNode | — | 自訂詳情頁內容;未提供時渲染 item.description |
backLabel | string | "返回" | 返回按鈕文字與無障礙標籤 |
ViewContinuityItem
| 欄位 | 型別 | 說明 |
|---|---|---|
id | string | 唯一識別,作為 View Transition 名稱與受控值的依據 |
title | string | 卡片與詳情頁共用、會跨頁延續的標題 |
image | string | 圖片來源(會跨頁延續變形) |
imageAlt | string | 圖片替代文字 |
eyebrow | string | 標題上方的小標(不跨頁延續) |
description | ReactNode | 詳情頁內文 |
細節
- 切換時以原生
document.startViewTransition()包裹狀態更新,並搭配flushSync讓 DOM 在同一幀內替換,瀏覽器才能正確捕捉前後兩個快照。 - 被點卡片的圖片與標題在兩個視圖共用同一組
view-transition-name,由瀏覽器負責跨頁變形;平時不掛名,只在過場當下才賦名,避免干擾頁面上其他的 View Transition。 - 其餘卡片依「與被點卡片的相對格位」計算向外退散的方向與距離,較遠的卡片退得較遠;返回時以動態注入的 keyframes 依距離錯開延遲,依序歸位。
- 不支援 View Transitions API 的瀏覽器,或使用者開啟「減少動態效果」時,直接即時切換視圖,功能完全不受影響。
可及性
- 卡片為原生
<button>,可聚焦並以 Enter/Space 觸發;網格為role="list"。 - 進入詳情頁時焦點移到返回鈕,返回網格時焦點歸還原本的卡片;詳情頁支援 Esc 返回。
- 詳情頁為
role="region"並以項目標題作為aria-label。 - 過場只使用
view-transition-name與 CSS 動畫,減少動態效果時完全略過動畫。