Frontend Lab
← 回到所有效果
F28I201framer-motion

Image Compare Slider

前後對比拖拉

難度 ★★★★★標籤 drag · compare · clip-path3 min read

效果簡述

左右兩張圖(或設計稿)疊在一起,拖中間分隔線比較 before / after。 攝影、設計改版、AI 修圖展示必備。核心是 clip-path: inset() 把上層 切成不同寬度。

BEFORE
AFTER
預設|中央拖拉

Claude Code Prompt

💬 Claude Code Prompt
參考:I201 image compare
技術:clip-path inset + motion value + 事件位置換算

具體行為:
- 父層 ref + overflow-hidden
- useMotionValue(50) 代表分隔線位置(百分比)
- useTransform 把 x 變成 clip-path: inset(0 ${100-v}% 0 0)
- onMouseMove / onTouchMove 算出 (clientX - rect.left) / rect.width * 100
- 限制 0~100,setX

限制:
- 兩張圖尺寸要一樣
- 必須 overflow-hidden 否則 clip-path 看起來像被切走
- 移動裝置用 onTouchMove,且 e.touches[0].clientX

完整程式碼

ImageCompare.tsx
'use client'

import { motion, useMotionValue, useTransform } from 'framer-motion'
import { useRef } from 'react'

export function ImageCompare() {
const ref = useRef<HTMLDivElement>(null)
const x = useMotionValue(50)
const clip = useTransform(x, (v) => `inset(0 ${100 - v}% 0 0)`)
const left = useTransform(x, (v) => `${v}%`)

function move(clientX: number) {
  const r = ref.current?.getBoundingClientRect()
  if (!r) return
  x.set(Math.max(0, Math.min(100, ((clientX - r.left) / r.width) * 100)))
}

return (
  <div
    ref={ref}
    className="relative h-44 cursor-ew-resize overflow-hidden rounded-xl border"
    onMouseMove={(e) => move(e.clientX)}
    onTouchMove={(e) => move(e.touches[0].clientX)}
  >
    <div className="absolute inset-0 bg-card">{/* BEFORE */}</div>
    <motion.div className="absolute inset-0 bg-orange-500" style={{ clipPath: clip }}>
      {/* AFTER */}
    </motion.div>
    <motion.div className="absolute inset-y-0 w-[2px] -translate-x-1/2 bg-white" style={{ left }}>
      <div className="absolute top-1/2 left-1/2 h-8 w-8 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white"></div>
    </motion.div>
  </div>
)
}

何時用

  • ✅ 設計改版 before / after
  • ✅ AI 修圖 / 上色展示
  • ✅ 攝影 retouch demo

何時別用

  • ❌ 兩張圖差異太小(看不出來)
  • ❌ 內容站(沒對比情境)
  • ❌ 主要受眾用螢幕閱讀器(替代方案:兩張並排)

我踩過的坑

  1. 使用 width % 而不是 clip-path 會 layout reflow:每次 mousemove resize 一個 div 整頁重排版。clip-path 只動 paint,順很多。

  2. 觸控事件忘記 onTouchMove:mobile 拖不動。要同時綁 mouse + touch。

  3. 沒 cursor-ew-resize 視覺暗示弱:要明確告訴使用者「可拖」。

相關效果

搜尋

按 ⌘K 隨時開啟