WebberUI

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.jsonfiles[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型別預設值說明
beforeReactNode下層內容(分割線左側),在文件流中撐開容器尺寸
afterReactNode上層內容(分割線右側),依分割位置以 clip-path 裁切
initialnumber50初始分割位置(0–100 百分比)
beforeLabelReactNode左上角標籤:進場淡入,分割線拖近時自動淡出避讓
afterLabelReactNode右上角標籤:進場淡入,分割線拖近時自動淡出避讓
onChange(value: number) => void分割位置改變時回呼(0–100)
classNamestring附加到最外層容器;建議在此給定尺寸與圓角

可及性

  • 把手是 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

On this page