PD12▸📊 Data Display · 資料展示▸輪播
Compare Slider
對比滑桿
難度 ★★★★★標籤 before-after · drag · apple3 min read
模式簡述
Compare Slider:拖曳中線比較 before/after 兩張圖。apple.com 產品比較、設計改版頁面常用。
Before
After
◆◆
Claude Code Prompt
參考:PD12 compare-slider 組件:CompareSlider.tsx Compare Slider:拖曳中線比較 before/after 兩張圖。apple.com 產品比較、設計改版頁面常用。 限制: - 沒做鍵盤支援 - handle 太細 - 圖片 aspect-ratio 不一致
完整程式碼
"use client";
import { useEffect, useRef, useState } from "react";
type State = "center" | "left" | "right";
export function CompareSlider({ state = "center" }: { state?: State }) {
const pct = state === "left" ? 25 : state === "right" ? 75 : 50;
const [pos, setPos] = useState(pct);
const ref = useRef<HTMLDivElement>(null);
const [dragging, setDragging] = useState(false);
useEffect(() => setPos(pct), [pct]);
useEffect(() => {
function onMove(e: PointerEvent) {
if (!dragging || !ref.current) return;
const rect = ref.current.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
setPos(Math.max(0, Math.min(100, x)));
}
function onUp() {
setDragging(false);
}
if (dragging) {
window.addEventListener("pointermove", onMove);
window.addEventListener("pointerup", onUp);
return () => {
window.removeEventListener("pointermove", onMove);
window.removeEventListener("pointerup", onUp);
};
}
}, [dragging]);
return (
<div className="w-full max-w-md">
<div
ref={ref}
className="relative aspect-[16/10] overflow-hidden rounded-xl border border-border select-none"
>
<div className="absolute inset-0 bg-gradient-to-br from-zinc-700 to-zinc-900">
<div className="absolute inset-0 grid place-items-center">
<span className="text-xs font-mono uppercase tracking-wider text-white/80">Before</span>
</div>
</div>
<div
className="absolute inset-y-0 left-0 overflow-hidden bg-gradient-to-br from-lab-accent via-pink-500 to-purple-600"
style={{ width: `${pos}%` }}
>
<div
className="absolute inset-0 grid place-items-center"
style={{ width: `${(100 / pos) * 100}%` }}
>
<span className="text-xs font-mono uppercase tracking-wider text-white">After</span>
</div>
</div>
<div
className="absolute inset-y-0 -ml-px w-0.5 cursor-ew-resize bg-white shadow-lg"
style={{ left: `${pos}%` }}
onPointerDown={() => setDragging(true)}
>
<div className="absolute left-1/2 top-1/2 grid h-8 w-8 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full bg-white shadow-md">
<span className="font-mono text-[10px] text-zinc-700">◆◆</span>
</div>
</div>
</div>
</div>
);
}何時用
- ✅ 產品改版前後比較
- ✅ 設計案例 case study
- ✅ AI 修圖前後對比
何時別用
- ❌ 三張以上的比較(不適用,改用並排)
- ❌ 需要文字註解的比較(用 grid)
- ❌ 手機端如果圖小(拖曳精準度差)
我踩過的坑
-
沒做鍵盤支援:方向鍵應該也可調 position。
-
handle 太細:1px 線手指找不到。中間放圓形 handle 至少 32px。
-
圖片 aspect-ratio 不一致:兩張圖比例不同會錯位。強制 object-cover + 同尺寸容器。
相關模式
- PD11
- PD09