Zoomable Density Grid
密度縮放網格:如 iOS 照片般切換每列欄數,所有格子以 FLIP 動畫平滑重排。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
2
8
<ZoomableDensityGrid />
安裝
npx shadcn@latest add https://webberui.com/r/zoomable-density-grid.json或在 components.json 設定 registries 後,改用 @webberui/zoomable-density-grid 安裝。
安裝依賴後,從 registry JSON(/r/zoomable-density-grid.json 的 files[0].content)複製 zoomable-density-grid.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge使用
import { ZoomableDensityGrid } from "@/components/ui/zoomable-density-grid";
<ZoomableDensityGrid densities={[2, 4, 6]} defaultDensity={4} gap={8}>
{photos.map((src) => (
<img
key={src}
src={src}
alt=""
className="aspect-square rounded-lg object-cover"
/>
))}
</ZoomableDensityGrid>每個直接子元素會自動包進 motion.div 動畫格子,不需要額外標記。
Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
densities | number[] | [2, 4, 6] | 密度檔位(每檔=每列欄數),依序顯示在切換器上 |
defaultDensity | number | densities[0] | 初始欄數,需為 densities 其中一檔 |
gap | number | 8 | 格子間距(px) |
children | React.ReactNode | — | 網格內容,每個直接子元素自動包進動畫格子 |
onDensityChange | (density: number) => void | — | 密度切換時觸發,帶入新的欄數 |
className | string | — | 附加到最外層容器的 class |
細節
- FLIP 重排:切換欄數時,每個格子以 Motion
layout動畫連續過渡位置與尺寸,而非淡出後重新出現 - 微縮回彈:切換瞬間所有格子帶短暫的 scale 微縮回彈(spring),模擬 iOS 照片縮放的手感
- 滑動底塊:切換器的選中底塊以
layoutId在檔位之間滑動 - 佈局動畫以 transform 實現,過渡期間格子內容會隨之短暫縮放;純色塊、圖片等內容視覺效果最佳
densities傳入的檔位會自動取整、下限 1 並去重;空陣列時退回預設[2, 4, 6]
可及性
- 使用者系統開啟「減少動態效果」時,FLIP 與底塊滑動停用,切換時直接跳到新佈局(DOM 結構與 SSR 一致)
- 切換器為原生
<button>群組(role="group"加aria-label),各檔位以aria-pressed標示選取狀態,並帶「每列 N 欄」的aria-label - 鍵盤可 Tab 循序聚焦各檔位,
focus-visible時顯示外框