Before and after image slider in pure CSS

rossangus

Ross Angus

Posted on November 29, 2023

Before and after image slider in pure CSS

Image by Sams.Wild

You've probably seen those before-and-after image sliders kicking about. You know, the old paint the house. Victor/Victoria. The switcharoo. There's no shortage of Wordpress plugins which you can pay $30 a month to access (imagine renting JavaScript!). I had a need to use one the other day, so naturally, I wanted to build it JavaScript free1.

To break a long tradition of recipe blogging, I'll cut directly to the chorus. Here's the Codepen:

The trick, such that it is, is to (ab)use the CSS resize property on a paragraph, so that the user has a means to slide one image over the other.

Advantages

  • Sweet, sweet semantic HTML
  • Zero JavaScript
  • Still legible if the user hits reading mode2

Disadvantages

  • The user interface isn't great (much of the CSS is used to generate the wee tooltip, which shows users where to grab)
  • It doesn't look like all the other image switcharoos
  • As far as I can work out, it's impossible to fix this, using just CSS and resize

How it works

There's really not a lot to it. Inside a figure element are two paragraphs and a figcaption. The figcaption is hidden away with position and the second paragraph establishes the height and width of the figure. The first paragraph sits on top, using position: absolute and can be resized using that cheeky resize: horizontal declaration.

The only minor trick is how to set the width of the first paragraph so it's exactly half the width of the second image. This is done by setting the display property of the figure to table. This lets the figure follow the normal document flow, but also shrink-wraps the content inside. In that way, it's similar to a float, but without all the impact on layout for following elements.

Finally, to make it responsive, a max-height of 100% is added to both images and the second image also has a max-width of 100%. This stops the horizontal scroll bar from appearing and scales both images down with the size of their container.

Most of the rest of the CSS does two things:

  1. Removes the figcaption from the document flow, so that it doesn't interfer with the height of the figure
  2. Generates the tooltip which shows the user where to grab, in order to slide the images over each other.

There's two small additions to the markup: the first paragraph has a tabindex of 0 and an autofocus attribute. This is so it immediately falls into focus, on page load, which displays the tooltip.

Because the resize handle doesn't appear to be part of the DOM, it's possible to resize the image with both the tooltip showing, or not. It depends where the focus is.

But none of this focus and tooltip code is required for the resize to work.

The second markup addition is to add a style attribute to the figure tag which sets a CSS variable of --start. This defaults to 50% and allows you to set a different initial width of the first image, when the page first loads.

Earlier attempt

When attempting to build this sort of thing, my usual starting point is what native HTML control kind of does this already? The one which first sprung to mind was an input of the type range.

Luckily, Ana Tudor has done all the hard work of styling range elements and has put them on CodePen. I forked one of them, removed all the IE styles and put an image on the part of the slider before the slider thumb, and a different image after the slider thumb.

Because I use God's Own Browser, this all came together quickly and worked well. It looked, and behaved, just like one of the JavaScript switcharoos. Then I tested it in Chrome.

After debugging for a while, it became clear that Tudor's range sliders use a tiny bit of JavaScript to make the parts of the slider track before and after the thumb different colours. Specifically, Tudor's code uses an image gradient to simulate that there are two different elements before and after the thumb. The gradient has a hard colour stop under the thumb and JavaScript is used to move the position of this stop, depending upon the value of the range.

I didn't replicate this, so here's the Firefox-only version of the image switcharoo:

This is less than ideal for reasons beyond the Internet's stubborn insistence on using an inferior web browser:

  • The images are defined in CSS - they are changed on a case-by-case basis by using CSS variables, but this is a clumsy fix3
  • In order to display the range control correctly, each instance of it needs a CSS variable representing the URL of each image, a width and a height. The figure based solution doesn't require the width and height.
  • The markup isn't very semantic - it's a range control, with no meaningful content inside
  • It doesn't work if the user hits the reading mode
  • Hacking form elements to perform CSS tricks is generally frowned upon in the accessibility community (perhaps an attribute of role="presentation" would help?)

Appendix: peeking behind the curtain

You know how when a civilian watches you code and you bring up the inspector pane and their mind is blown? They get a glimpse into what they probably call The Matrix and see a baffling jumble of code and controls for things they couldn't even imagine.

What if I told you there was a further level we could go down? I'm not talking about view source. I'm talking about burrowing into the contents of the shadow DOM itself.

Not the shadow DOM that you might create using your own code. But the shadow DOM which the browser natively uses, to build up form elements.

Go back to my fork of Tudor's range control for a moment. Look at all those weird pseudo-elements which appear in the CSS - stuff like -webkit-slider-runnable-track, -moz-range-track, -moz-range-progress and -webkit-slider-thumb. You can probably find documentation of these elements somewhere, but there's also an option to show them directly in the inspector.

Exploring the shadow DOM in Chrome

In Chrome, it's an option which you can find by doing the following:

  • Open Inspector
  • Click on the cog, top right, to get to the settings
  • In Preferences (on the tabs on the left - preferences should be open by default anyway), scroll down until you see the Elements heading
  • Make sure the checkbox next to Show user agent shadow DOM is checked

Here's what you'll see, if you inspect the range slider example:

The HTML source in the inspector panel in Chrome. The shadow DOM node inside the input element has been expanded to show pseudo-elements inside.

Exploring the shadow DOM in Firefox

In Firefox, it's a tiny bit more complicated:

  • Type about:config into the address bar. You'll see a scary message, warning you are about to step off the safe path, and start into the thickets of Extreme Browser Hacking
  • Type devtools.inspector.showAllAnonymousContent into the search box and you should see the configuration setting appear below
  • You can toggle the setting on or off using that funny little icon which looks like an arrow pointing both ways at the same time

Here's what you'll see, if you inspect the range slider example:

The HTML source in the inspector panel in Firefox. The shadow DOM node inside the input element has been expanded to show pseudo-elements inside.

Not quite as useful as Chrome, unfortunately, as it doesn't name the pseudo-elements.


  1. Because if JavaScript costs money to rent, the only way to make it free would be to use zero JavaScript 

  2. You poor Chrome users won't know the joy the rest of us feel when a cookie control applies a blur filter to a web page and we make it all go away with a single click on our toolbar. Well, there was a brief window when you could feel this joy, but it's been switched off now. 

  3. CSS variables are wonderful but should be used to pass around style information, not content. Technically, the path to each image would count as content. 

💖 💪 🙅 🚩
rossangus
Ross Angus

Posted on November 29, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related