Image Compare
拖曳中央分割線比較前後兩層內容,以 clip-path 裁切、spring 平滑跟隨。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
50
<ImageCompare />
安裝
npx shadcn@latest add https://webberui.com/r/image-compare.json安裝依賴後,從 registry JSON(/r/image-compare.json 的 files[0].content)複製 image-compare.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge使用
before / after 接受任意 ReactNode——最常見是圖片,但放影片、程式碼區塊或整段 UI 都可以。before 在下層、顯示於分割線左側;after 在上層、依分割位置裁切後顯示於右側。建議在 className 給定容器尺寸(如 aspect-video),並讓兩層內容以 h-full w-full 填滿:
import { ImageCompare } from "@/components/ui/image-compare";
<ImageCompare
before={
<img src="/photo-before.jpg" alt="調色前" className="h-full w-full object-cover" />
}
after={
<img src="/photo-after.jpg" alt="調色後" className="h-full w-full object-cover" />
}
beforeLabel="Before"
afterLabel="After"
initial={40}
onChange={(value) => console.log(value)}
className="aspect-video w-full max-w-2xl rounded-xl"
/>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
before | ReactNode | — | 下層內容(分割線左側),在文件流中撐開容器尺寸 |
after | ReactNode | — | 上層內容(分割線右側),依分割位置以 clip-path 裁切 |
initial | number | 50 | 初始分割位置(0–100 百分比) |
beforeLabel | ReactNode | — | 左上角標籤:進場淡入,分割線拖近時自動淡出避讓 |
afterLabel | ReactNode | — | 右上角標籤:進場淡入,分割線拖近時自動淡出避讓 |
onChange | (value: number) => void | — | 分割位置改變時回呼(0–100) |
className | string | — | 附加到最外層容器;建議在此給定尺寸與圓角 |
可及性
- 把手是
role="slider"的可聚焦元素,帶aria-valuemin/aria-valuemax/aria-valuenow/aria-valuetext,輔助科技會即時朗讀目前分割百分比 - 鍵盤操作:Tab 聚焦把手後,← / ↓ 減 2%、→ / ↑ 加 2%,Home 跳到 0、End 跳到 100
- 使用者系統開啟「減少動態效果」時,分割線不經 spring 平滑、直接跳到目標位置,標籤也不播進場動畫
細節
- spring 平滑跟隨:分割位置存在
useMotionValue(百分比),畫面經useSpring平滑後驅動clip-path: inset()與分割線left,拖曳時帶一點慣性尾勁 - 整個容器都是熱區:pointer 按下任一處即跳到該位置並開始拖曳(
setPointerCapture讓拖出容器外也不中斷),不必精準抓到把手 - 觸控支援:把手設
touch-action: none,手機上從把手拖曳不會誤觸發頁面捲動;容器並攔截原生圖片拖曳(dragstart)避免中斷手勢 - 標籤避讓:兩角標籤的透明度由分割位置驅動——分割線拖近 8–22%(左)或 78–92%(右)區間時漸進淡出,不遮擋比較內容
- 動畫只跑在
clip-path與 transform 上,不觸發 layout