Overlaysoverlaysimagecomparesliderbefore-after
Image Compare Slider
Split-view slider to compare before/after images.
Live Preview
โนโบ
Usage
Copy and paste this code into your React component:
tsx
import { ImageCompareSlider } from 'react-component-kit-ui/overlays';
<ImageCompareSlider beforeImage="/assets/before.webp" afterImage="/assets/after.webp" beforeLabel="Before" afterLabel="After" />Manual Installation
If you are not using the npm package, create a new file and paste the following code: src/components/ImageCompareSlider.tsx + ImageCompareSlider.css:
ImageCompareSlider.tsx
'use client';
import { useEffect, useRef } from 'react';
import type { CSSProperties } from 'react';
import './ImageCompareSlider.css';
/** "a: b; --c: d" โ style object */
function st(css: string | undefined): CSSProperties {
const o: Record<string, string> = {};
for (const decl of String(css ?? '').split(';')) {
const i = decl.indexOf(':');
if (i < 0) continue;
const k = decl.slice(0, i).trim();
if (k) o[k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = decl.slice(i + 1).trim();
}
return o as CSSProperties;
}
/**
* ImageCompareSlider โ Interactive dual-image split compare slider with draggable separator bar.
*
* @param {string} beforeImage - URL of original / before image.
* @param {string} afterImage - URL of modified / after image.
* @param {string} beforeLabel - Label tag. Default is 'Before'.
* @param {string} afterLabel - Label tag. Default is 'After'.
* @param {number} initialPosition - Starting separator position (0 to 100). Default is 50.
*/
export interface ImageCompareSliderProps {
beforeImage?: string;
afterImage?: string;
beforeLabel?: string;
afterLabel?: string;
initialPosition?: number;
}
export function ImageCompareSlider({
beforeImage = 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=800&auto=format&fit=crop&q=80',
afterImage = 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=800&auto=format&fit=crop&q=80',
beforeLabel = 'Before (Wireframe)',
afterLabel = 'After (Production)',
initialPosition = 50,
}: ImageCompareSliderProps) {
// Ported from the Astro <script>: runs once after mount (DOM logic kept as in the Astro kit).
const mounted = useRef(false);
useEffect(() => {
if (mounted.current) return;
mounted.current = true;
function setupImageCompare() {
document.querySelectorAll('[data-img-compare]').forEach((comp) => {
const range = comp.querySelector('[data-compare-range]') as HTMLInputElement;
if (!range) return;
range.addEventListener('input', () => {
(comp as HTMLElement).style.setProperty('--pos', `${range.value}%`);
});
});
}
setupImageCompare();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<div className="img-compare" data-img-compare style={st(`--pos: ${initialPosition}%;`)} data-rk-886e9f="">
<div className="img-compare__layer img-compare__layer--after" data-rk-886e9f="">
<img src={afterImage} alt={afterLabel} loading="lazy" data-rk-886e9f="" />
<span className="img-compare__tag img-compare__tag--after" data-rk-886e9f="">{afterLabel}</span>
</div>
<div className="img-compare__layer img-compare__layer--before" data-compare-before data-rk-886e9f="">
<img src={beforeImage} alt={beforeLabel} loading="lazy" data-rk-886e9f="" />
<span className="img-compare__tag img-compare__tag--before" data-rk-886e9f="">{beforeLabel}</span>
</div>
<div className="img-compare__handle" data-compare-handle aria-hidden="true" data-rk-886e9f="">
<div className="handle-line" data-rk-886e9f=""></div>
<div className="handle-button" data-rk-886e9f="">
<span data-rk-886e9f="">โน</span>
<span data-rk-886e9f="">โบ</span>
</div>
</div>
<input type="range" min="0" max="100" defaultValue={initialPosition} className="img-compare__range" aria-label="Drag to compare images" data-compare-range data-rk-886e9f="" />
</div>
</>
);
}
export default ImageCompareSlider;ImageCompareSlider.css
/* ImageCompareSlider โ ported from astro-component-kit (overlays/ImageCompareSlider.astro) */
.img-compare:where([data-rk-886e9f]) {
position: relative;
width: 100%;
max-width: 600px;
aspect-ratio: 16 / 10;
overflow: hidden;
border-radius: var(--r-xl, 20px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
user-select: none;
font-family: var(--font-sans, system-ui, sans-serif);
}
.img-compare__layer:where([data-rk-886e9f]) {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.img-compare__layer:where([data-rk-886e9f]) img:where([data-rk-886e9f]) {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
pointer-events: none;
}
.img-compare__layer--before:where([data-rk-886e9f]) {
clip-path: polygon(0 0, var(--pos) 0, var(--pos) 100%, 0 100%);
}
.img-compare__tag:where([data-rk-886e9f]) {
position: absolute;
top: 14px;
padding: 4px 10px;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(8px);
color: #ffffff;
font-size: 0.72rem;
font-weight: 700;
border-radius: var(--r-full, 9999px);
border: 1px solid rgba(255, 255, 255, 0.15);
pointer-events: none;
}
.img-compare__tag--before:where([data-rk-886e9f]) { left: 14px; }
.img-compare__tag--after:where([data-rk-886e9f]) { right: 14px; }
.img-compare__handle:where([data-rk-886e9f]) {
position: absolute;
top: 0;
bottom: 0;
left: var(--pos);
transform: translateX(-50%);
pointer-events: none;
z-index: 5;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.handle-line:where([data-rk-886e9f]) {
width: 2px;
height: 100%;
background: #ffffff;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.8);
}
.handle-button:where([data-rk-886e9f]) {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 36px;
height: 36px;
border-radius: 50%;
background: #ffffff;
color: #050505;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
font-size: 0.95rem;
font-weight: 900;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
}
.img-compare__range:where([data-rk-886e9f]) {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: ew-resize;
z-index: 10;
}Quick Info
- Category
- Overlays
- Filename
ImageCompareSlider.tsx- Dependencies
- None โ pure React + CSS
- Tags
- overlaysimagecomparesliderbefore-after