before/after 比較スライダー

透明な range と clip-path で2枚の画像を境界線で切り替えるビフォーアフター比較。加工前後やリフォーム事例の紹介に。

#css#clip-path#range#interactive

ライブデモ

使用例(お題: カフェ MOON BREW)

この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- MOON BREW:ラテアート ビフォーアフター比較スライダー -->
<section class="mb-ba">
  <div class="mb-ba__head">
    <span class="mb-ba__tag">BARISTA SCHOOL</span>
    <h2 class="mb-ba__title">3日でここまで描ける。</h2>
    <p class="mb-ba__lead">体験レッスン受講前と受講後の一杯。<br>スライダーを動かして上達を比べてみて。</p>
  </div>

  <!-- スライダーで2枚を切り替え -->
  <figure class="mb-ba__stage">
    <img class="mb-ba__img mb-ba__img--after" src="https://picsum.photos/720/520?random=21" alt="受講後のラテアート">
    <div class="mb-ba__before" id="mbClip">
      <img class="mb-ba__img" src="https://picsum.photos/720/520?random=22&grayscale" alt="受講前のラテ">
      <span class="mb-ba__lbl mb-ba__lbl--before">BEFORE</span>
    </div>
    <span class="mb-ba__lbl mb-ba__lbl--after">AFTER</span>
    <span class="mb-ba__line" id="mbLine"><span class="mb-ba__knob">◀▶</span></span>
    <input class="mb-ba__range" id="mbRange" type="range" min="0" max="100" value="50" aria-label="比較スライダー">
  </figure>
</section>
CSS
/* MOON BREW:ラテアート ビフォーアフタースライダー */
:root {
  --cream: #f5ede1;
  --brown: #2b1d12;
  --amber: #c98a3b;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  display: flex;
  align-items: center;
  gap: 26px;
  padding: 0 28px;
  font-family: "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background: var(--cream);
  color: var(--brown);
  overflow: hidden;
}

.mb-ba__head { flex: 0 0 210px; }
.mb-ba__tag { font-size: 10px; letter-spacing: 0.3em; color: var(--amber); }
.mb-ba__title {
  margin: 10px 0 12px;
  font-size: 23px;
  line-height: 1.45;
  font-family: "Hiragino Mincho ProN", "Yu Mincho", serif;
}
.mb-ba__lead { margin: 0; font-size: 12.5px; line-height: 1.9; color: #6d5b49; }

/* 比較ステージ */
.mb-ba__stage {
  position: relative;
  flex: 1;
  height: 300px;
  margin: 0;
  border-radius: 18px;
  overflow: hidden;
  box-shadow: 0 16px 36px rgba(43,29,18,0.18);
}
.mb-ba__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.mb-ba__img--after { filter: saturate(1.1); }

/* before レイヤーを幅でクリップ */
.mb-ba__before {
  position: absolute;
  inset: 0;
  width: 50%;
  overflow: hidden;
  border-right: 2px solid var(--cream);
}
.mb-ba__before .mb-ba__img { width: 720px; max-width: none; }

.mb-ba__lbl {
  position: absolute;
  top: 14px;
  font-size: 10px;
  letter-spacing: 0.18em;
  padding: 5px 11px;
  border-radius: 999px;
  font-weight: 700;
}
.mb-ba__lbl--before { left: 14px; background: rgba(43,29,18,0.6); color: #fff; }
.mb-ba__lbl--after { right: 14px; background: var(--amber); color: #2b1d12; }

/* 仕切り線とノブ */
.mb-ba__line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  background: var(--cream);
  transform: translateX(-1px);
  pointer-events: none;
}
.mb-ba__knob {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 10px;
  letter-spacing: -1px;
  color: #fff;
  background: var(--amber);
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

/* 透明な range を全面に重ねて操作 */
.mb-ba__range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
}
JavaScript
// range の値で before レイヤー幅と仕切り線位置を更新
const range = document.getElementById("mbRange");
const clip = document.getElementById("mbClip");
const line = document.getElementById("mbLine");

if (range && clip && line) {
  const update = () => {
    const v = Number(range.value) || 0; // 0〜100
    clip.style.width = `${v}%`;
    line.style.left = `${v}%`;
  };
  range.addEventListener("input", update);
  update(); // 初期反映(50%)
}

コード

HTML
<!-- before/after 比較スライダー -->
<div class="stage">
  <div class="ba" aria-label="ビフォーアフター比較">
    <!-- 下層: After(カラー) -->
    <img class="ba__img" src="https://picsum.photos/id/1016/900/600" alt="アフター(加工後)" draggable="false">
    <!-- 上層: Before(モノクロ)。幅を変えて見せる量を調整 -->
    <div class="ba__before">
      <img class="ba__img" src="https://picsum.photos/id/1016/900/600?grayscale" alt="ビフォー(加工前)" draggable="false">
      <span class="ba__tag ba__tag--before">BEFORE</span>
    </div>
    <span class="ba__tag ba__tag--after">AFTER</span>

    <!-- ドラッグ用ハンドル -->
    <input class="ba__range" type="range" min="0" max="100" value="50"
           aria-label="比較位置スライダー">
    <div class="ba__handle" aria-hidden="true">
      <span class="ba__grip"></span>
    </div>
  </div>
</div>
CSS
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  background: radial-gradient(circle at 50% 0%, #1b2230, #0c0e14 70%);
  font-family: "Segoe UI", system-ui, sans-serif;
}
.stage { padding: 24px; }

/* 比較枠。--pos(%) で境界位置を共有 */
.ba {
  --pos: 50%;
  position: relative;
  width: min(72vw, 440px);
  aspect-ratio: 3 / 2;
  border-radius: 14px;
  overflow: hidden;
  user-select: none;
  box-shadow: 0 18px 45px -15px rgba(0, 0, 0, .7);
}
.ba__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* before レイヤーは右端を clip して幅を可変に */
.ba__before {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
}

/* ラベル */
.ba__tag {
  position: absolute;
  top: 12px;
  z-index: 3;
  padding: 4px 12px;
  font-size: 11px;
  letter-spacing: .2em;
  font-weight: 800;
  color: #fff;
  background: rgba(12, 14, 20, .6);
  backdrop-filter: blur(4px);
  border-radius: 999px;
}
.ba__tag--before { left: 12px; }
.ba__tag--after { right: 12px; }

/* 実体は透明な range。当たり判定として全面に置く */
.ba__range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
  z-index: 4;
}

/* 見た目のハンドル(CSS のみ、--pos に追従) */
.ba__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 2px;
  background: #fff;
  transform: translateX(-1px);
  z-index: 2;
  pointer-events: none;
  box-shadow: 0 0 12px rgba(0, 0, 0, .5);
}
.ba__grip {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 38px;
  height: 38px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .4);
  display: grid;
  place-items: center;
}
/* 左右の三角矢印 */
.ba__grip::before,
.ba__grip::after {
  content: "";
  width: 0;
  height: 0;
  border-block: 5px solid transparent;
  position: absolute;
}
.ba__grip::before { border-right: 7px solid #1b2230; left: 8px; }
.ba__grip::after { border-left: 7px solid #1b2230; right: 8px; }

.ba__range:focus-visible ~ .ba__handle .ba__grip {
  outline: 3px solid #8ab4ff;
  outline-offset: 2px;
}
JavaScript
// range の値を --pos に反映し、before レイヤーとハンドルを同期させる
const ba = document.querySelector(".ba");
const range = ba && ba.querySelector(".ba__range");

if (ba && range) {
  // スライダー値(%)を CSS 変数へ
  const update = () => {
    ba.style.setProperty("--pos", `${range.value}%`);
  };

  range.addEventListener("input", update);
  // 初期反映
  update();
}

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「before/after 比較スライダー」の効果を追加してください。

# 追加してほしい効果
before/after 比較スライダー(画像エフェクト)
透明な range と clip-path で2枚の画像を境界線で切り替えるビフォーアフター比較。加工前後やリフォーム事例の紹介に。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- before/after 比較スライダー -->
<div class="stage">
  <div class="ba" aria-label="ビフォーアフター比較">
    <!-- 下層: After(カラー) -->
    <img class="ba__img" src="https://picsum.photos/id/1016/900/600" alt="アフター(加工後)" draggable="false">
    <!-- 上層: Before(モノクロ)。幅を変えて見せる量を調整 -->
    <div class="ba__before">
      <img class="ba__img" src="https://picsum.photos/id/1016/900/600?grayscale" alt="ビフォー(加工前)" draggable="false">
      <span class="ba__tag ba__tag--before">BEFORE</span>
    </div>
    <span class="ba__tag ba__tag--after">AFTER</span>

    <!-- ドラッグ用ハンドル -->
    <input class="ba__range" type="range" min="0" max="100" value="50"
           aria-label="比較位置スライダー">
    <div class="ba__handle" aria-hidden="true">
      <span class="ba__grip"></span>
    </div>
  </div>
</div>

【CSS】
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  background: radial-gradient(circle at 50% 0%, #1b2230, #0c0e14 70%);
  font-family: "Segoe UI", system-ui, sans-serif;
}
.stage { padding: 24px; }

/* 比較枠。--pos(%) で境界位置を共有 */
.ba {
  --pos: 50%;
  position: relative;
  width: min(72vw, 440px);
  aspect-ratio: 3 / 2;
  border-radius: 14px;
  overflow: hidden;
  user-select: none;
  box-shadow: 0 18px 45px -15px rgba(0, 0, 0, .7);
}
.ba__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* before レイヤーは右端を clip して幅を可変に */
.ba__before {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
}

/* ラベル */
.ba__tag {
  position: absolute;
  top: 12px;
  z-index: 3;
  padding: 4px 12px;
  font-size: 11px;
  letter-spacing: .2em;
  font-weight: 800;
  color: #fff;
  background: rgba(12, 14, 20, .6);
  backdrop-filter: blur(4px);
  border-radius: 999px;
}
.ba__tag--before { left: 12px; }
.ba__tag--after { right: 12px; }

/* 実体は透明な range。当たり判定として全面に置く */
.ba__range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
  z-index: 4;
}

/* 見た目のハンドル(CSS のみ、--pos に追従) */
.ba__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 2px;
  background: #fff;
  transform: translateX(-1px);
  z-index: 2;
  pointer-events: none;
  box-shadow: 0 0 12px rgba(0, 0, 0, .5);
}
.ba__grip {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 38px;
  height: 38px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .4);
  display: grid;
  place-items: center;
}
/* 左右の三角矢印 */
.ba__grip::before,
.ba__grip::after {
  content: "";
  width: 0;
  height: 0;
  border-block: 5px solid transparent;
  position: absolute;
}
.ba__grip::before { border-right: 7px solid #1b2230; left: 8px; }
.ba__grip::after { border-left: 7px solid #1b2230; right: 8px; }

.ba__range:focus-visible ~ .ba__handle .ba__grip {
  outline: 3px solid #8ab4ff;
  outline-offset: 2px;
}

【JavaScript】
// range の値を --pos に反映し、before レイヤーとハンドルを同期させる
const ba = document.querySelector(".ba");
const range = ba && ba.querySelector(".ba__range");

if (ba && range) {
  // スライダー値(%)を CSS 変数へ
  const update = () => {
    ba.style.setProperty("--pos", `${range.value}%`);
  };

  range.addEventListener("input", update);
  // 初期反映
  update();
}

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。