1 звезда на GitHub = 1 новый компонент.

Поставить звезду
Модальные окнаoverlaysimagecomparesliderbefore-after

Image Compare Slider

Интерактивный слайдер сравнения двух изображений «До и После».

Живое превью

After (Production)After (Production)
Before (Wireframe)Before (Wireframe)

Использование

Скопируйте и вставьте этот код в ваш React-компонент:

tsx
import { ImageCompareSlider } from 'react-component-kit-ui/overlays';

<ImageCompareSlider beforeImage="/assets/before.webp" afterImage="/assets/after.webp" beforeLabel="Before" afterLabel="After" />

Ручная установка

Если вы не используете пакет npm, создайте файл и вставьте следующий код: 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;
}

Краткая информация

Категория
Модальные окна
Имя файла
ImageCompareSlider.tsx
Зависимости
Нет — чистый React + CSS
Теги
overlaysimagecomparesliderbefore-after