【JavaScript】ビフォーアフターの作り方 - ドラッグ画像比較の実装

2枚の画像を左右に表示し、境界線をドラッグ(スワイプ)することで比較する「ビフォーアフター比較」を作る方法です。コピペで簡単に実装できます!

2枚の画像を用意

画像A
ビフォー
画像B
アフター

ビフォーとアフターの2枚の画像を用意します。サイズは違っても構いませんが、アスペクト比を合わせておく必要があります

ビフォーアフター画像比較のサンプル

Before After

初期状態は←Slide→という帯を横揺れアニメーションで表示し、「スライドできる画像だよ~」ということをアピールします。
領域内をドラッグしている最中は帯が消えます。カーソルを離すと静止状態の←Slide→が再出現し、境界線のハンドルとして機能します。

カスタマイズ例

Before After

境界線と帯の色、画像の表示サイズは簡単にカスタマイズできます。

また、ビフォーアフター領域はレスポンシブ対応となっており、指定した画像サイズよりも画面が小さい場合、画面幅に合わせて自動的にリサイズします。様々なデバイスで最適な表示が可能です。

ソースコード

JavaScriptコード

<script>
(function() {
    function initBeforeAfter() {
        const containers = document.querySelectorAll('.before-after-container');

        containers.forEach(container => {
            const beforeImage = container.querySelector('.before-image');
            const afterImage = container.querySelector('.after-image');
            const sliderHandle = container.querySelector('.slider-handle');

            let isDragging = false;
            let containerWidth;

            function updateSliderPosition(percentage) {
                const clampedPercentage = Math.max(0, Math.min(100, percentage));
                sliderHandle.style.left = `${clampedPercentage}%`;
                afterImage.style.clipPath = `polygon(${clampedPercentage}% 0, 100% 0, 100% 100%, ${clampedPercentage}% 100%)`;
            }

            function handleDrag(clientX) {
                if (!isDragging) return;
                const containerRect = container.getBoundingClientRect();
                const position = clientX - containerRect.left;
                const percentage = (position / containerRect.width) * 100;
                updateSliderPosition(percentage);
            }

            function startDragging(e) {
                isDragging = true;
                sliderHandle.classList.add('active');
                sliderHandle.classList.add('used');
                e.preventDefault();
                handleDrag(e.type.includes('mouse') ? e.clientX : e.touches[0].clientX);
            }

            function stopDragging() {
                isDragging = false;
                sliderHandle.classList.remove('active');
            }

            sliderHandle.addEventListener('mousedown', startDragging);
            sliderHandle.addEventListener('touchstart', startDragging);

            document.addEventListener('mousemove', (e) => handleDrag(e.clientX));
            document.addEventListener('touchmove', (e) => handleDrag(e.touches[0].clientX));

            document.addEventListener('mouseup', stopDragging);
            document.addEventListener('touchend', stopDragging);

            function setContainerSize() {
                const maxWidth = parseInt(container.getAttribute('data-max-width') || '100%');
                const viewportWidth = Math.min(document.documentElement.clientWidth, window.innerWidth);
                containerWidth = Math.min(maxWidth, viewportWidth);
                container.style.width = `${containerWidth}px`;
            }

            function initializeContainer() {
                setContainerSize();
                updateSliderPosition(50);
            }

            if (beforeImage.complete && afterImage.complete) {
                initializeContainer();
            } else {
                beforeImage.onload = afterImage.onload = initializeContainer;
            }
        });
    }

    // DOMContentLoaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initBeforeAfter);
    } else {
        initBeforeAfter();
    }
})();
</script>

JavaScriptコードは</body>の前に入れます。

HTMLコード

<div class="before-after-container" data-max-width="300">
    <img src="before.jpg" alt="Before" class="before-image">
    <img src="after.jpg" alt="After" class="after-image">
    <div class="slider-handle"></div>
</div>

ビフォーアフターの横幅はdata-max-width="300"の数字をイジると調整できます。 画像のアスペクト比に応じて、縦幅は自動的に決まります。

スマホなどの画面が小さいデバイスにおいて、data-max-widthに設定した値がデカい場合は、画面の横幅に合わせて自動的にリサイズされます。

CSSコード

.before-after-container {
    --border-color: #ffffff; /* 境界線の色 */
    --slide-bg-color: #ffff00; /* スライドテキストの背景色 */
    --slide-text-color: #000000; /* スライドテキストの文字色 */
    --slide-text: "←Slide→"; /* スライドのテキスト */
    --border-width: 4px; /* 境界線の幅 */
    position: relative;
    display: inline-block;
    overflow: hidden;
    width: 100%;
    max-width: 100%;
}

.before-image, .after-image {
    display: block;
    width: 100%;
    height: auto;
}

.after-image {
    position: absolute;
    top: 0;
    left: 0;
    clip-path: polygon(50% 0, 100% 0, 100% 100%, 50% 100%);
}

.slider-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: var(--border-width);
    background: var(--border-color);
    cursor: ew-resize;
    z-index: 10;
}

.slider-handle::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 30px; /* 当たり判定を広げる幅 */
    transform: translateX(-50%);
    background: transparent;
    pointer-events: auto;
}

.slider-handle::after {
    content: var(--slide-text);
    position: absolute;
    top: 50%;
    left: 50%;
    background: var(--slide-bg-color);
    color: var(--slide-text-color);
    transform: translate(-50%, -50%);
    padding: 5px;
    border-radius: 5px;
    font-size: 14px;
    white-space: nowrap;
    opacity: 0.8;
    transition: opacity 0.3s ease;
    font-weight: bold;
    pointer-events: auto;
    animation: slideText 0.5s ease-in-out infinite;
}

.slider-handle.active::after {
    opacity: 0;
}

.slider-handle.used::after {
    animation: none;
}

@keyframes slideText {
    0%, 100% { transform: translate(-50%, -50%) translateX(-2px); }
    50% { transform: translate(-50%, -50%) translateX(2px); }
}

境界線や←Slide→の帯のカスタマイズは2行目から6行目で行います。

44行目のwidth: 30px;は「当たり判定」を設定しています。
わずか4pxの細い境界線の上をドラッグするのが意外とムズい!ってことで、境界線の上に「透明の当たり判定領域」を設けました。30pxの値を変更することで、当たり判定の範囲を調整できます。

CSSで帯の演出変更

Before After

CSSを少しイジって、スライダーの見た目を変更しました。
初期表示時は←Slide→の帯を表示し、一度操作した後は丸い●マークに切り替わります。

CSSの.slider-handle.used::after、つまり73~75行目を下記のものに置き換えることで実現できます。

CSSコード変更

.slider-handle.used::after {
  animation: none;
  content: "";
  width: 15px;
  height: 15px;
  opacity: 1;
  border-radius: 50%;
  background: var(--border-color);
}

-JavaScript