Before / After Slider
Two images stacked on top of each other: drag the handle, or use the arrow keys, to wipe between them. It can sway on its own until someone touches it.
Before
After
How to use it
- Paste the snippet just before
</head>. - Add
data-before-after-sliderto a container holding two images (or any two elements): before first, after second.
Options
Add any of these attributes to the same element.
data-start | Starting position, in percent. Default 50. |
data-autoplay | Sway gently until the visitor drags it. |
The handle is a proper slider: focus it and use the arrow keys (Shift for bigger steps), Home and End.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Before / After Slider: two images stacked; drag the handle (or use the
// arrow keys) to wipe between them. Optionally sways on its own until touched.
// Usage:
// <div data-before-after-slider>
// <img src="before.jpg" alt="Before">
// <img src="after.jpg" alt="After">
// </div>
// Optional: data-start (percent, default 50), data-autoplay (sway gently until
// the visitor drags it).
var DEFAULTS = { start: 50 };
var css =
'.bas{position:relative;display:grid;grid-template:1fr/1fr;overflow:hidden;touch-action:pan-y;user-select:none;-webkit-user-select:none}' +
'.bas>*:not(.bas-handle){grid-area:1/1;width:100%;height:100%;object-fit:cover}' +
'.bas>img{display:block}' +
'.bas-handle{position:absolute;top:0;bottom:0;width:44px;margin-left:-22px;cursor:ew-resize;display:grid;place-items:center;outline:none}' +
'.bas-handle::before{content:"";position:absolute;top:0;bottom:0;left:50%;width:2px;margin-left:-1px;background:#fff;box-shadow:0 0 8px rgba(0,0,0,.3)}' +
'.bas-knob{position:relative;width:36px;height:36px;border-radius:50%;background:#fff;box-shadow:0 2px 10px rgba(0,0,0,.25);display:grid;place-items:center;color:#333;font:600 14px/1 system-ui,sans-serif}' +
'.bas-handle:focus-visible .bas-knob{box-shadow:0 0 0 3px #a855f7}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function setup(el) {
var d = el.dataset;
var panes = Array.from(el.children).slice(0, 2);
if (panes.length < 2) return;
el.classList.add('bas');
var after = panes[1];
var handle = document.createElement('div');
handle.className = 'bas-handle';
handle.setAttribute('role', 'slider');
handle.setAttribute('tabindex', '0');
handle.setAttribute('aria-label', 'Before and after');
handle.setAttribute('aria-valuemin', '0');
handle.setAttribute('aria-valuemax', '100');
handle.innerHTML = '<span class="bas-knob" aria-hidden="true">↔</span>';
el.appendChild(handle);
var state = { pos: +d.start || DEFAULTS.start };
var sway = null;
function render() {
after.style.clipPath = 'inset(0 0 0 ' + state.pos + '%)';
handle.style.left = state.pos + '%';
handle.setAttribute('aria-valuenow', Math.round(state.pos));
}
function stopSway() { if (sway) { sway.pause(); sway = null; } }
function moveTo(p, smooth) {
stopSway();
p = Math.max(0, Math.min(100, p));
if (!smooth) { state.pos = p; return render(); }
anime.animate(state, { pos: p, duration: 450, ease: 'out(3)', onUpdate: render });
}
function fromPointer(e) {
var r = el.getBoundingClientRect();
return ((e.clientX - r.left) / r.width) * 100;
}
var dragging = false;
el.addEventListener('pointerdown', function (e) {
dragging = true;
el.setPointerCapture(e.pointerId);
moveTo(fromPointer(e), e.target !== handle && !handle.contains(e.target));
});
el.addEventListener('pointermove', function (e) { if (dragging) moveTo(fromPointer(e), false); });
el.addEventListener('pointerup', function () { dragging = false; });
el.addEventListener('pointercancel', function () { dragging = false; });
handle.addEventListener('keydown', function (e) {
var step = e.shiftKey ? 10 : 2;
if (e.key === 'ArrowLeft') { moveTo(state.pos - step, true); e.preventDefault(); }
if (e.key === 'ArrowRight') { moveTo(state.pos + step, true); e.preventDefault(); }
if (e.key === 'Home') { moveTo(0, true); e.preventDefault(); }
if (e.key === 'End') { moveTo(100, true); e.preventDefault(); }
});
render();
var autoplay = d.autoplay !== undefined && d.autoplay !== 'false';
if (autoplay && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
sway = anime.animate(state, {
pos: [{ to: 25, duration: 1600 }, { to: 75, duration: 2400 }, { to: 50, duration: 1600 }],
ease: 'inOut(2)', loop: true, loopDelay: 600, onUpdate: render
});
}
}
function init() {
document.querySelectorAll('[data-before-after-slider]').forEach(function (el) {
if (el._bas) return;
el._bas = true;
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-before-after-slider>
<img src="before.jpg" alt="Before">
<img src="after.jpg" alt="After">
</div>