lightweight image comparison slider

How to build a responsive and lightweight image comparison slider using JavaScript

by Lalji Tadhani

HTML

<div class="image-slider-wrapper">

  <div class="image-slider">
    <div class="top-image">
      <img src="https://source.unsplash.com/800x400?nature" alt="Nature" class="comparison-img after">
    </div>
    <img src="https://source.unsplash.com/800x400?tech" alt="Tech" class="comparison-img before">

    <div class="handle"></div>

    <input type="range" class="range-slider" min="0" max="100" value="50"/>
  </div>

  <div class="caption">
    Move the slider to toggle a comparison between two images.
  </div>
</div>

SCSS

.image-slider-wrapper {
  max-width: 100%;
  overflow: hidden;
  margin: 30px auto;
  .caption {
    padding: 20px;
    text-align: center;
    font-size:14px;
    opacity: .7;
  }
}
.image-slider {
  position: relative;
  display: block;
  width: 100%;
  min-height: 600px;
  height: 50vh;
  display: flex;
  align-content: flex-end;
  > div {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 50%;
    overflow: hidden;
    display: flex;
    align-items: flex-end;
  }
  .comparison-img {
    display: block;
    user-select: none;
    box-sizing: border-box;
    height: 100%;
    width: 100%;
    max-width: initial;
    filter: grayscale(100%);
    opacity: 0.5;
    pointer-events: none;
    object-fit: cover;
    flex: none;
  }
  .top-image {
    position: relative;
    z-index: 10;
    &:before {
      content: "";
      position: absolute;
      top: 0;
      right: 0;
      width: 5px;
      height: 100%;
      border-right: 5px solid #FFF;
      z-index: 20;
    }
  }

  //this will act as the controller for the slider
  .handle {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 20px;
    background-color: #666666;
    height: 80px;
    border: 8px solid #FFF;
    z-index: 30;
    pointer-events: none;
    margin-left: -2px;
    overflow: visible;
    &:before {
      color: #FFF;
      content: "";
      position: absolute;
      top: 25px;
      right: -20px;
      height: 10px;
      width: 10px;
      border-bottom: solid 3px currentColor;
      border-right: solid 3px currentColor;
      transform: rotate(-45deg);
      z-index: 99;
    }
    &:after {
      color: #FFF;
      content: "";
      position: absolute;
      top: 25px;
      left: -20px;
      height: 10px;
      width: 10px;
      border-left: solid 3px currentColor;
      border-top: solid 3px currentColor;
      transform: rotate(-45deg);
      z-index: 99;
    }
  }
  input[type=range] {
    margin: 0;
   ...

JavaScript

function imageSlider(elem) {
    var imageContainer = elem;
    var overlay = imageContainer.querySelector(".top-image");
    var range = imageContainer.querySelector(".range-slider");
    var images = imageContainer.querySelectorAll(".comparison-img");
    var handle = imageContainer.querySelector(".handle");
    images.forEach(function(elem) {
      elem.style.width = range.offsetWidth + 'px'
    })
    range.oninput = function() {
      if ( this.value > 0 ) {
        overlay.style.width = this.value + "%";
        handle.style.left = this.value + "%";
      } else {
        overlay.style.width = 'calc(0% + 5px)';
        handle.style.left = 'calc(0% + 5px)';
      }
    }
  }

  const sliders = document.querySelectorAll('.image-slider-wrapper');
  function initImageSliders() {
    console.log('test')
    for (var i = 0; i < sliders.length; i++) {
      imageSlider(sliders[i]);
    }
  }

  initImageSliders();
  window.onresize = initImageSliders;
  window.dispatchEvent(new Event('resize'));