← Cards & images

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

  1. Paste the snippet just before </head>.
  2. Add data-before-after-slider to a container holding two images (or any two elements): before first, after second.

Options

Add any of these attributes to the same element.

data-startStarting position, in percent. Default 50.
data-autoplaySway 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.

1 · Snippet
<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">&#x2194;</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>
2 · Your HTML
<div data-before-after-slider>
  <img src="before.jpg" alt="Before">
  <img src="after.jpg" alt="After">
</div>