Introduction

This post walks through building a before and after image slider that works on desktop and touch devices.

Get started with the HTML

We'll get started by adding the basic HTML required for the before and after image slider.

before-and-after.html

<!-- before and after html with tailwind css classes -->
<div class="overflow-hidden before-after-wrapper rounded-3xl">
  <!-- before image: width starts at 50% but will change depending on where the slider is -->
  <div class="before-image-wrapper" style="width: 50%;">
    <div class="before-image" style="background-image: url('https://images.prismic.io/zachpatrick/ZrFKAUaF0TcGIsrh_light-mode.webp?auto=format,compress');"></div>
  </div>
  
  <!-- after image: takes up entire width -->
  <div class="after-image" style="background-image: url('https://images.prismic.io/zachpatrick/ZrFKAEaF0TcGIsrg_dark-mode.webp?auto=format,compress');"></div>

  <!-- draggable slider with handle -->
  <span class="handle draggable"></span>
</div>

Adding the functionality with JavaScript

Now we can add all the functionality with JavaScript.

We're going to listen for any user interactions with the sliders .handle and based on that, we'll update the width of the before image.

It's important that we keep in mind devices that allow touch input (smartphones, tablets, etc.), as well as devices with non-touch inputs (desktop computers). The first time I built something like this I neglected touch devices and didn't catch the error for quite some time 😬.

before-and-after.js

// get the div containing both the before and after images
const imageWrapper = document.querySelector(`.before-after-wrapper`);
// check if the user is using a touch device
const isTouch = window.matchMedia('(pointer: coarse)').matches;

// initialize the dragElement function below
dragElement(imageWrapper.querySelector('.handle'));

// get the current coordinates of the .handle
function getCoords(e) {
  let x, y;

  // get the current x and y of the users input (dragging .handle)
  // check if the device is a touch device
  if (isTouch) {
    // touchscreen: if the devices is a touch device, the x and y will be in the touches[0] object
    x = e.touches[0].clientX;
    y = e.touches[0].clientY;
  } else {
    // 
    // desktop: if the device is not a touch device we can get it right from the event (e) object
    x = e.clientX;
    y = e.clientY;
  }

  return { x, y };
}

// listens for the user touch/mouse input on the .handle element and drags the slider
function dragElement(el) {
  let pos1 = 0,
      pos2 = 0,
      pos3 = 0,
      pos4 = 0;

  // initialize the slider being dragged when the .handle is pressed
  if (isTouch) {
    el.ontouchstart = dragInit;
  } else {
    el.onmousedown = dragInit;
  }

  // call elementDrag/closeElementDrag when the users interacts with .handle
  function dragInit(e) {
    e = e || window.event;
    e.preventDefault();

    // get the mouse cursor/touch position at startup
    const { x, y } = getCoords(e);
    pos3 = x;
    pos4 = y;

    if (isTouch) {
      document.ontouchend = closeElementDrag; // stop moving
      document.ontouchmove = elementDrag; // call function whenever the cursor moves
    } else {
      document.onmouseup = closeElementDrag; // stop moving
      document.onmousemove = elementDrag; // call function whenever the cursor moves
    }
  }

  // as the element is dragged update the .before-image-wrapper width
  function elementDrag(e) {
    e = e || window.event;
    e.preventDefault();

    // calculate new cursor position:
    const { x, y } = getCoords(e);
    pos1 = pos3 - x;
    pos2 = pos4 - y;
    pos3 = x;
    pos4 = y;

    let wrapperRight = el.offsetLeft - pos1;

    if (wrapperRight >= 0 && wrapperRight <= imageWrapper.offsetWidth) {
      // set the element's new position:
      el.style.left = `${el.offsetLeft - pos1}px`;
      imageWrapper.querySelector('.before-image-wrapper').style.width = `${wrapperRight}px`;
    }
  }

  function closeElementDrag() {
    if (isTouch) {
      document.ontouchend = null;
      document.ontouchmove = null;
    } else {
      document.onmouseup = null;
      document.onmousemove = null;
    }
  }
}

Styling the before and after slider with CSS

Lastly, we can add some CSS to make things look pretty.

before-and-after.css

.before-after-wrapper {
  outline: 4px solid green;
  position: relative;
  aspect-ratio: 3/2;
}

.before-after-wrapper::before {
  background-color: hsla(172, 61%, 82%, 75%);
  border-radius: 40px;
  top: 10px;
  color: green;
  content: 'before';
  left: 10px;
  opacity: 0;
  padding: 5px 10px;
  position: absolute;
  transition: opacity 200ms ease-in-out;
  z-index: 15;
}

:global(.before-after-wrapper.show-before::before) {
  opacity: 1;
}

.before-after-wrapper::after {
  background-color: hsla(172, 61%, 82%, 75%);
  border-radius: 40px;
  top: 10px;
  color: green;
  opacity: 0;
  padding: 5px 10px;
  position: absolute;
  right: 10px;
  transition: opacity 200ms ease-in-out;
  z-index: 15;
  content: 'after';
}

:global(.before-after-wrapper.show-after::after) {
  opacity: 1;
}

.before-image-wrapper {
  bottom: 0;
  left: 0;
  overflow: hidden;
  position: absolute;
  top: 0;
  z-index: 10;
}

.before-image {
  background-size: cover;
  height: 100%;
  width: 100%;
}

.after-image {
  background-size: cover;
  inset: 0;
  position: absolute;
}

.handle {
  align-items: center;
  background: green;
  bottom: 0;
  cursor: col-resize;
  display: flex;
  justify-content: center;
  left: 50%;
  position: absolute;
  top: 0;
  width: 4px;
  z-index: 20;
}

.handle::before {
  content: '';
  position: absolute;
  inset: 0 -20px;
}

.handle::after {
  align-items: center;
  background: darkgreen;
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 512 512"><path fill="hsl(172, 61%, 82%)" d="M505.7 265.7c3-3 3.1-7.9 .2-11.1l-104-112c-3-3.2-8.1-3.4-11.3-.4s-3.4 8.1-.4 11.3L481.7 252 23.3 252l90.3-90.3c3.1-3.1 3.1-8.2 0-11.3s-8.2-3.1-11.3 0l-104 104c-3.1 3.1-3.1 8.2 0 11.3l104 104c3.1 3.1 8.2 3.1 11.3 0s3.1-8.2 0-11.3L23.3 268l457.4 0-90.3 90.3c-3.1 3.1-3.1 8.2 0 11.3s8.2 3.1 11.3 0l104-104z"/></svg>');
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 50%;
  color: green;
  content: '';
  display: flex;
  font-size: 16px;
  font-weight: bold;
  justify-content: center;
  max-height: 40px;
  max-width: 40px;
  min-height: 40px;
  min-width: 40px;
}

Example

Was this post helpful?