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

ビフォー

アフター
ビフォーとアフターの2枚の画像を用意します。サイズは違っても構いませんが、アスペクト比を合わせておく必要があります。
ビフォーアフター画像比較のサンプル
初期状態はという帯を横揺れアニメーションで表示し、「スライドできる画像だよ~」ということをアピールします。
領域内をドラッグしている最中は帯が消えます。カーソルを離すと静止状態のが再出現し、境界線のハンドルとして機能します。
カスタマイズ例
境界線と帯の色、画像の表示サイズは簡単にカスタマイズできます。
また、ビフォーアフター領域はレスポンシブ対応となっており、指定した画像サイズよりも画面が小さい場合、画面幅に合わせて自動的にリサイズします。様々なデバイスで最適な表示が可能です。
ソースコード
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); }
}
境界線やの帯のカスタマイズは2行目から6行目で行います。
44行目のwidth: 30px;は「当たり判定」を設定しています。
わずか4pxの細い境界線の上をドラッグするのが意外とムズい!ってことで、境界線の上に「透明の当たり判定領域」を設けました。30pxの値を変更することで、当たり判定の範囲を調整できます。
CSSで帯の演出変更
CSSを少しイジって、スライダーの見た目を変更しました。
初期表示時はの帯を表示し、一度操作した後は丸い●マークに切り替わります。
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);
}


