Frontend Lab
← 回到所有模式
PD12📊 Data Display · 資料展示輪播

Compare Slider

對比滑桿

難度 ★★★★★標籤 before-after · drag · apple3 min read

模式簡述

Compare Slider:拖曳中線比較 before/after 兩張圖。apple.com 產品比較、設計改版頁面常用。

Before
After
◆◆
50/50|對半分

Claude Code Prompt

💬 Claude Code Prompt
參考:PD12 compare-slider
組件:CompareSlider.tsx

Compare Slider:拖曳中線比較 before/after 兩張圖。apple.com 產品比較、設計改版頁面常用。

限制:
- 沒做鍵盤支援
- handle 太細
- 圖片 aspect-ratio 不一致

完整程式碼

CompareSlider.tsx
"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)
  • ❌ 手機端如果圖小(拖曳精準度差)

我踩過的坑

  1. 沒做鍵盤支援:方向鍵應該也可調 position。

  2. handle 太細:1px 線手指找不到。中間放圓形 handle 至少 32px。

  3. 圖片 aspect-ratio 不一致:兩張圖比例不同會錯位。強制 object-cover + 同尺寸容器。

相關模式

  • PD11
  • PD09

搜尋

按 ⌘K 隨時開啟