@use 'sprucecss/scss/spruce' as *;
.image-compare {
--position: 50%;
margin: 0;
position: relative;
@include layout-stack('xs');
&__frame {
border-radius: radius('lg');
display: grid;
overflow: hidden;
position: relative;
}
&__image {
block-size: 100%;
grid-area: 1 / 1;
inline-size: 100%;
object-fit: cover;
user-select: none;
&--after {
clip-path: inset(0 0 0 var(--position));
}
}
&__handle {
background-color: hsl(0deg 0% 100%);
box-shadow: 0 0 0 1px hsl(0deg 0% 0% / 20%);
inline-size: 3px;
inset-block: 0;
inset-inline-start: var(--position);
pointer-events: none;
position: absolute;
translate: -50% 0;
&::after {
background-color: hsl(0deg 0% 100%);
block-size: 2.5rem;
border-radius: 50%;
box-shadow: shadow('md');
color: hsl(0deg 0% 10%);
content: '↔';
display: grid;
font-weight: 700;
inline-size: 2.5rem;
inset-block-start: 50%;
inset-inline-start: 50%;
place-content: center;
position: absolute;
translate: -50% -50%;
}
}
&__tag {
background-color: hsl(0deg 0% 0% / 55%);
border-radius: radius('pill');
color: hsl(0deg 0% 100%);
font-size: config('font-size-sm', $typography);
inset-block-start: spacer('s');
padding-block: spacer('xxs');
padding-inline: spacer('s');
position: absolute;
&--before {
inset-inline-start: spacer('s');
}
&--after {
inset-inline-end: spacer('s');
}
}
&__range {
appearance: none;
background: transparent;
block-size: calc(100% - 2rem);
cursor: ew-resize;
inline-size: 100%;
inset-block-start: 0;
inset-inline-start: 0;
margin: 0;
opacity: 0;
position: absolute;
&:focus-visible {
opacity: 0;
}
}
&:has(&__range:focus-visible) &__frame {
outline: 2px solid color('focus-ring');
outline-offset: 3px;
}
&__caption {
color: color('muted');
font-size: config('font-size-sm', $typography);
text-align: center;
}
}
[dir='rtl'] .image-compare__image--after {
clip-path: inset(0 var(--position) 0 0);
}
[dir='rtl'] .image-compare__handle {
translate: 50% 0;
}
<figure class="image-compare" style="--position: 50%;" data-image-compare>
<div class="image-compare__frame">
<img class="image-compare__image" src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=1200&q=70&sat=-100" alt="The mountains in black and white (before)." width="1200" height="800">
<img class="image-compare__image image-compare__image--after" src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=1200&q=70" alt="The mountains in color (after)." width="1200" height="800">
<span class="image-compare__handle" aria-hidden="true"></span>
<span class="image-compare__tag image-compare__tag--before" aria-hidden="true">Before</span>
<span class="image-compare__tag image-compare__tag--after" aria-hidden="true">After</span>
</div>
<label class="sr-only" for="image-compare-range">Comparison position</label>
<input class="image-compare__range" id="image-compare-range" type="range" min="0" max="100" value="50" data-image-compare-range>
<figcaption class="image-compare__caption">Drag the handle (or use the arrow keys) to compare the photos.</figcaption>
</figure>
(() => {
document.querySelectorAll('[data-image-compare]').forEach((component) => {
const range = component.querySelector('[data-image-compare-range]');
const update = () => component.style.setProperty('--position', `${range.value}%`);
range.addEventListener('input', update);
update();
});
})();