前后对比滑块

在图片上拖动分隔线,对比修改后的版本和下面的原图。

  • JS
  • 图片
  • 拖拽
  • 收录于
修改前修改后

AI 提示词

用 React 制作一个无障碍的前后对比图片组件。叠放两张同尺寸的图片;“修改后”的图片在上层,并用 clip-path: inset(0 calc(100% - var(--pos) * 1%) 0 0) 裁切。在整张图片上覆盖一个 opacity 0 的原生 <input type="range">(0–100,aria-label 为“对比修改前后”),由它处理拖动、触摸和方向键,并把数值写入 --pos。在 left: calc(var(--pos) * 1%) 处画一条 2px 的分隔线和一个 40px 的圆形手柄,手柄内放两个小箭头。在两个角落显示“修改前”和“修改后”标签,被分隔线遮住时淡出。用键盘聚焦滑块时,在手柄上显示焦点环。初始位置为 50%。裁切本身不加过渡,让它精确跟随指针。

源代码

Demo.tsx
'use client'

import { useState } from 'react'
import s from './demo.module.css'

export default function BeforeAfter({ labels }: { labels: Record<string, string> }) {
  const [pos, setPos] = useState(50)

  return (
    <div className={s.frame} style={{ '--pos': pos } as React.CSSProperties}>
      <div className={`${s.scene} ${s.before}`} aria-hidden="true" />
      <div className={`${s.scene} ${s.after}`} aria-hidden="true" />
      <span className={`${s.tag} ${s.tagBefore}`} data-hidden={pos < 22 || undefined}>
        {labels.before}
      </span>
      <span className={`${s.tag} ${s.tagAfter}`} data-hidden={pos > 78 || undefined}>
        {labels.after}
      </span>
      <span className={s.divider} aria-hidden="true">
        <span className={s.handle}>‹ ›</span>
      </span>
      <input
        type="range"
        min={0}
        max={100}
        value={pos}
        aria-label={labels.label}
        className={s.input}
        onChange={(e) => setPos(Number(e.target.value))}
      />
    </div>
  )
}
demo.module.css
.frame {
  /* scene palette: a CSS-drawn landscape stands in for a photo */
  --sky-top: oklch(78% 0.08 70);
  --sky-low: oklch(70% 0.13 50);
  --sun: oklch(92% 0.09 85);
  --hill-far: oklch(48% 0.07 40);
  --hill-near: oklch(30% 0.04 45);
  position: relative;
  width: min(300px, 100%);
  aspect-ratio: 3 / 2;
  max-width: 100%;
  overflow: hidden;
  border-radius: 14px;
  user-select: none;
}

.scene {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 70% 40% at 25% 105%, var(--hill-near) 98%, transparent 100%),
    radial-gradient(ellipse 80% 45% at 85% 100%, var(--hill-far) 98%, transparent 100%),
    radial-gradient(circle at 68% 42%, var(--sun) 0 11%, transparent 11.5%),
    linear-gradient(180deg, var(--sky-top), var(--sky-low));
}

.before { filter: grayscale(1) contrast(0.85) brightness(0.9); }
.after { clip-path: inset(0 calc(100% - var(--pos) * 1%) 0 0); }

.tag {
  position: absolute;
  top: 10px;
  padding: 4px 8px;
  border-radius: 6px;
  background: var(--stage-bg);
  color: var(--stage-ink);
  font: 500 var(--text-xs) / 1 var(--font-mono);
  transition: opacity 150ms linear;
}

.tagAfter { left: 10px; }
.tagBefore { right: 10px; }
.tag[data-hidden] { opacity: 0; }

.divider {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--pos) * 1%);
  width: 2px;
  margin-left: -1px;
  background: var(--stage-ink);
  pointer-events: none;
}

.handle {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--stage-ink);
  color: var(--stage-bg);
  font: 700 var(--text-base) / 1 var(--font-body);
  transform: translate(-50%, -50%);
}

.input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
}

.input:focus-visible ~ .divider .handle,
.frame:has(.input:focus-visible) .handle { outline: 2px solid var(--color-focus); outline-offset: 3px; }

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。