Isotype Proportion Grid
「每 X 中有 Y」的圖標比例方陣:數百個小圖示隨滾動以波浪方式變色點亮,並逐步驟切換不同的比例敘述。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
100
10
0.14
<IsotypeProportionGrid />
安裝
npx shadcn@latest add https://webberui.com/r/isotype-proportion-grid.json或在 components.json 設定 registries 後,改用 @webberui/isotype-proportion-grid 安裝。
安裝依賴後,從 registry JSON(/r/isotype-proportion-grid.json 的 files[0].content)複製 isotype-proportion-grid.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge使用
import {
IsotypeProportionGrid,
type ProportionStep,
} from "@/components/ui/isotype-proportion-grid";
const steps: ProportionStep[] = [
{ numerator: 90, denominator: 100, label: "90% 每天使用手機", color: "#6366f1" },
{ numerator: 63, denominator: 100, label: "63% 通勤時聽音樂", color: "#14b8a6" },
{ numerator: 27, denominator: 100, label: "27% 每週閱讀", color: "#f59e0b" },
];
<IsotypeProportionGrid steps={steps} total={100} columns={10} />;元件會撐出 stepScrollHeight × 步驟數 的捲動高度,並以 sticky 面板釘住方陣。捲動時,填充前緣依 waveOrder 掃過整個網格,對應比例的圖示逐一由中性色變為點亮色;比例下降時,多餘的圖示以反向波紋熄滅。頂部文案在跨越步驟邊界時交叉淡入。
巢狀捲動容器
若元件放在具 overflow 的容器內(而非整頁捲動),將該容器的 ref 傳入 container,並用 viewportClassName 指定 sticky 面板高度以貼合容器:
const scrollerRef = React.useRef<HTMLDivElement>(null);
<div ref={scrollerRef} className="h-[320px] overflow-y-auto">
<IsotypeProportionGrid
steps={steps}
container={scrollerRef}
stepScrollHeight="240px"
viewportClassName="h-[320px]"
/>
</div>;自訂圖示與掃掠方向
icon 接受任何 { className } 圖示元件(含 lucide-react),可用 waveOrder 切換波紋的掃掠方式:
import { Heart } from "lucide-react";
<IsotypeProportionGrid steps={steps} icon={Heart} waveOrder="radial" />;Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
steps | ProportionStep[] | — | 各步驟的比例敘述,隨滾動依序切換 |
total | number | 100 | 圖示方陣的總格數 |
columns | number | 10 | 網格欄數 |
iconSize | number | 20 | 單一圖示邊長(px) |
gap | number | 6 | 圖示間距(px) |
icon | IsotypeGlyph | 內建人形 | 預設圖示元件(可傳 lucide-react) |
activeColor | string | "#6366f1" | 點亮顏色,可被步驟的 color 覆寫 |
waveOrder | "row" | "column" | "diagonal" | "radial" | "diagonal" | 波次點亮的掃掠方向 |
waveWidth | number | 0.14 | 波峰寬度(同時過渡的圖示佔比,0–1) |
revealFraction | number | 0.6 | 每個步驟視窗用於掃掠的比例,其餘停留 |
stepScrollHeight | string | "90svh" | 每步驟分配的捲動高度(CSS 長度) |
showDots | boolean | true | 是否顯示可點擊跳轉的步驟指示點 |
onStepChange | (index: number, step: ProportionStep) => void | — | 目前步驟變更時的回呼 |
container | RefObject<HTMLElement | null> | — | 巢狀捲動容器的 ref;省略時以視窗為捲動容器 |
aria-label | string | "圖標比例格" | 無障礙標籤 |
className | string | — | 套用在最外層捲動包裹容器 |
gridClassName | string | — | 套用在網格容器 |
viewportClassName | string | "h-[100svh]" | 套用在 sticky 檢視面板(用於指定其高度) |
ProportionStep
| 欄位 | 型別 | 說明 |
|---|---|---|
numerator | number | 分子:每組中命中的數量 |
denominator | number | 分母:每組總數 |
label | ReactNode | 主標題;省略時自動生成「每 {denominator} 中有 {numerator}」 |
caption | ReactNode | 補充說明文字 |
color | string | 此步驟的點亮顏色,覆寫 activeColor |
icon | IsotypeGlyph | 此步驟的圖示,覆寫元件層級 icon |
細節
- 每格的排名
rank = 排序後位置 / total,其掃掠順序由waveOrder決定(diagonal對角、radial由中心外擴等)。當填充比例跨過某格的 rank,恰好點亮對應數量的圖示,因此「63%」即精準點亮round(0.63 × total)格。 - 捲動進度以分段映射轉為填充比例:每個步驟視窗的前
revealFraction用於掃掠到新比例,其餘用於停留,因此文案穩定時方陣也維持在完整比例。 - 點亮色由網格父層以
animate帶動,步驟間換色時平滑補間;各格的顯露則純由捲動位置驅動,向上捲回會反向倒帶。 - 下方指示點可點擊,會平滑捲動到對應步驟的停留區(自動判斷是視窗或巢狀容器捲動)。
可及性
- 具
aria-live="polite"的隱藏區域,於步驟切換時朗讀目前的比例敘述。 - 方陣本身為裝飾性重複圖形,標記
aria-hidden,資訊改由文案與 live 區域傳達。 - 步驟指示點為具
aria-label的按鈕,標示目前步驟(aria-current)並具鍵盤聚焦樣式。 - 使用者系統開啟「減少動態效果」時,改為靜態堆疊版面:每個步驟各自呈現正確比例的方陣,不套用 sticky 與波次動畫。