<figure class="image-comparison js-image-comparison">
    <img class="image-comparison__image" src="/assets/images/lens-detail-neutralgrey.jpg" alt="With">
    <div class="image-comparison__resize js-image-comparison-resize">
        <img class="image-comparison__image image-comparison__image--resize" src="/assets/images/lens-detail-without.jpg" alt="With">
    </div>
    <span class="image-comparison__handle js-image-comparison-handle"></span>
    <figcaption class="image-comparison__caption image-comparison__caption--left">Without</figcaption>
    <figcaption class="image-comparison__caption image-comparison__caption--center">Images are not retouched</figcaption>
    <figcaption class="image-comparison__caption image-comparison__caption--right">With</figcaption>
</figure>
<figure class="image-comparison js-image-comparison">
  <img class="image-comparison__image" src="{{imageRight.src}}" alt="{{imageRight.caption}}">
  <div class="image-comparison__resize js-image-comparison-resize">
    <img class="image-comparison__image image-comparison__image--resize" src="{{imageLeft.src}}" alt="{{imageRight.caption}}">
  </div>
  <span class="image-comparison__handle js-image-comparison-handle"></span>
  <figcaption class="image-comparison__caption image-comparison__caption--left">{{imageLeft.caption}}</figcaption>
  {{#if caption}}
  <figcaption class="image-comparison__caption image-comparison__caption--center">{{caption}}</figcaption>
  {{/if}}
  <figcaption class="image-comparison__caption image-comparison__caption--right">{{imageRight.caption}}</figcaption>
</figure>
{
  "imageLeft": {
    "src": "/assets/images/lens-detail-without.jpg",
    "caption": "Without"
  },
  "imageRight": {
    "src": "/assets/images/lens-detail-neutralgrey.jpg",
    "caption": "With"
  },
  "caption": "Images are not retouched"
}
  • Content:
    (function (window) {
      'use strict';
    
      function getPageX(e) {
        if (e.type.indexOf('touch') === 0) {
          return e.touches[0].pageX;
        }
    
        return e.pageX;
      }
    
      function handleDrag(e) {
        // Elements.
        const dragElement = e.target;
        const container = dragElement.parentNode;
        const resizeElement = container.querySelector('.js-image-comparison-resize');
    
        // Classes.
        const dragClass = 'js-draggable';
    
        // Values used by calculations.
        const dragWidth = dragElement.offsetWidth;
        const xPosition = dragElement.getBoundingClientRect().left + document.body.scrollLeft + dragWidth - getPageX(e);
        const containerOffset = container.getBoundingClientRect().left + document.body.scrollLeft;
        const containerWidth = container.offsetWidth;
        const minLeft = containerOffset + 10;
        const maxLeft = containerOffset + containerWidth - dragWidth - 10;
    
        // State.
        let dragging = false;
    
        // The user is moving the draggable element.
        function handleDragMove(e) {
          if (!dragging) {
            dragging = true;
    
            requestAnimationFrame(function () {
              let leftValue = getPageX(e) + xPosition - dragWidth;
    
              // Constrain the draggable element to only move within the container.
              if (leftValue < minLeft) {
                leftValue = minLeft;
              } else if (leftValue > maxLeft) {
                leftValue = maxLeft;
              }
    
              const widthValue = (leftValue + dragWidth / 2 - containerOffset) * 100 / containerWidth + '%';
    
              dragElement.style.left = widthValue;
              resizeElement.style.width = widthValue;
    
              dragging = false;
            });
          }
        }
    
        // The user has let go.
        function stopDrag(e) {
          container.removeEventListener('mousemove', handleDragMove);
          container.removeEventListener('touchmove', handleDragMove);
          dragElement.classList.remove(dragClass);
        }
    
        dragElement.classList.add(dragClass);
    
        container.addEventListener('mousemove', handleDragMove);
        container.addEventListener('touchmove', handleDragMove);
        container.addEventListener('mouseup', stopDrag);
        container.addEventListener('touchend', stopDrag);
    
        e.preventDefault();
      }
    
      function init() {
        const containers = document.querySelectorAll('.js-image-comparison');
    
        for (let i = 0; i < containers.length; i++) {
          if (!containers[i].classList.contains('js-component-init')) {
            const handle = containers[i].querySelector('.js-image-comparison-handle');
    
            handle.addEventListener('mousedown', handleDrag);
            handle.addEventListener('touchstart', handleDrag);
    
            containers[i].classList.add('js-component-init');
          }
        }
      }
    
      // Allow application JS to reinitialize any instances added with Ajax, etc.
      if (typeof componentEvents !== 'undefined') {
        componentEvents.on('component-init', init);
      }
    
      init();
    
    })(this);
    
  • URL: /components/raw/image-comparison/image-comparison.js
  • Filesystem Path: src/components/01-elements/image-comparison/image-comparison.js
  • Size: 2.9 KB
  • Content:
    .image-comparison {
      position: relative;
      width: 100%;
      margin: 0;
    }
    
    .image-comparison__image {
      display: block;
      width: 100%;
    }
    
    .image-comparison__resize {
      position: absolute;
      top: 0;
      left: 0;
      width: calc(50% - 1px);
      height: 100%;
      // Force hardware acceleration.
      transform: translateZ(0);
      border-right: 2px solid $color-white;
      overflow: hidden;
      backface-visibility: hidden;
    }
    
    .image-comparison__image--resize {
      display: block;
      position: absolute;
      top: 0;
      left: 0;
      width: auto;
      max-width: none;
      height: 100%;
    }
    
    .image-comparison__handle {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 30px;
      height: 30px;
      margin-top: -15px;
      margin-left: -15px;
      transform: translate3d(0, 0, 0) scale(1);
      transition: transform .3s .7s, opacity 0s .7s;
      border-radius: 50%;
      background: $color-darkest no-repeat center center;
      box-shadow: 0 0 0 6px rgba(37, 37, 37, .1);
      cursor: move;
    
      &.js-draggable {
        background-color: $color-black;
      }
    
      // Chevrons within the handle.
      &:before,
      &:after {
        display: block;
        position: absolute;
        top: 12px;
        width: 6px;
        height: 6px;
        border-width: 1px 1px 0 0;
        border-style: solid;
        border-color: $color-white;
        content: '';
        pointer-events: none;
      }
    
      &:before {
        left: 8px;
        transform: rotate(-135deg);
      }
    
      &:after {
        right: 8px;
        transform: rotate(45deg);
      }
    }
    
    .image-comparison__caption {
      margin-top: 5px;
      font-size: 1.4rem;
    }
    
    .image-comparison__caption--left {
      float: left;
    }
    
    .image-comparison__caption--center {
      display: none;
      position: absolute;
      right: 0;
      left: 0;
      font-weight: $font-weight-bold;
      text-align: center;
    
      @include breakpoint($breakpoint-md) {
        display: block;
      }
    }
    
    .image-comparison__caption--right {
      float: right;
    }
    
  • URL: /components/raw/image-comparison/image-comparison.scss
  • Filesystem Path: src/components/01-elements/image-comparison/image-comparison.scss
  • Size: 1.8 KB

This element was built by converting this demo to vanilla JS and adapting it to the project’s needs. CSS-only solutions to achieve this functionality are not well supported due to a dependence on the resize property.