Image comparison slider in 6 lines of JavaScript

While I was writing this post, I wanted to create an image comparison component. I made one with just a few lines of JavaScript, but I didn't include it in the post.

Here is the finished slider, with images from the book Letters from Sarajevo. If you want to play with the code, you can find it on CodePen.

Try out the debug mode (I'm quite proud of it! The shadow effect is my favorite detail.) . It reveals the structure in 3D and might give you a clue as to how I managed to create the slider with only a few lines of JavaScript.

Here's a tip - the blue element is actually a native HTML range input.

It's in the name

Image comparison slider. Slider, you say? We already have a native slider component - input [type=range]. Could we use it to make an image comparison slider? Turns out we can.

I think that the solution I came up with is clever, yet simple to understand.

This is the base idea:

  • We have two images and the range slider, stacked on each other.
  • The range input goes from 0 to 100.
  • As the slider is moved, we update a CSS variable to match the slider value.
  • We use this variable to set the width of the top image.

Here is a simplified, unstyled example. Try moving the slider to see how it works.

JavaScript

As we saw in the example above, everything is about keeping the slider value synced with the CSS variable. This variable controls the width of the top image and the position of the separator.

Here is the JavaScript code that keeps the --slider-value variable in sync with the slider value:

// Set element variables
const compareSlider = document.querySelector(".comparison-slider");
const input = compareSlider.querySelector(".comparison-slider__input");

// Update the CSS variable on input change
input.addEventListener("input", () => {
  compareSlider.style.setProperty("--slider-value", `${input.value}%`);
});

// Update the CSS variable on load
// to ensure the slider starts in sync with image's width.
compareSlider.style.setProperty("--slider-value", `${input.value}%`);

We could have been cheeky and shortened this code even more, but I favor readability. The code is still super short and easy to understand.

Here is another example for good measure, this time images are from The Tiny Book of Great Joys:

Browser support

I've tested it in Firefox, Chrome, and Safari on desktop, as well as Safari on mobile. It works smoothly in all of them. I'm pretty sure it will work on other browser too, but I haven't tested it myself.

If this post gets some traction, I'll probably clean it up and release a small library.

Accessibility

A nice thing about using native elements is that they are accessible by default. The range input is accessible and works with keyboard and screen readers. However, we need to add labels for the range input and the component itself.

I think something like this would work well:

<div
  class="comparison-slider"
  aria-label="
    Image comparison slider showing [image 1 description] and [image 2 description].
    Use the slider to compare the images."
  >
  <!-- Images and separator go here -->

  <label for="comparison-slider-input" id="comparison-slider-label">
    Adjust slider to compare images
  </label>
  <input type="range" id="comparison-slider-input" aria-labelledby="comparison-slider-label" />
</div>

Conclusion

I love using native features whenever possible. I even use them to solve problems beyond their intended purpose. By doing this I avoid re-implementing features that are already built in, keeping the code short and efficient.

If you are interested in this topic, you might like these posts too: