涟漪按压
从按下的位置扩散出一圈柔和的圆形并逐渐消失,让按钮在被点击的地方给出回应。
用 CSS 为 React 按钮制作按压涟漪效果。在 pointerdown 时,读取指针相对按钮的位置,并在该位置插入一个 <span>(position absolute,12px 圆形,translate -50% -50%,背景为 25% 不透明度的 currentColor)。用 CSS keyframe 在 600ms 内从 scale(0)、opacity 1 过渡到 scale(24)、opacity 0,缓动为 cubic-bezier(0.16, 1, 0.3, 1),并在 animationend 时移除该 span。按钮需要 overflow: hidden 和 position: relative。在 prefers-reduced-motion 下,用 120ms 的背景色变化代替涟漪。按钮文字为“保存修改”。
源代码
'use client'
import s from './demo.module.css'
export default function RipplePress({ labels }: { labels: Record<string, string> }) {
const onDown = (e: React.PointerEvent<HTMLButtonElement>) => {
const r = e.currentTarget.getBoundingClientRect()
const dot = document.createElement('span')
dot.className = s.ripple
dot.style.left = `${e.clientX - r.left}px`
dot.style.top = `${e.clientY - r.top}px`
dot.addEventListener('animationend', () => dot.remove())
e.currentTarget.appendChild(dot)
}
return (
<button type="button" className={s.button} onPointerDown={onDown}>
{labels.cta}
</button>
)
}
.button {
position: relative;
overflow: hidden;
height: 52px;
padding: 0 32px;
border: 0;
border-radius: var(--radius-sm);
background: var(--stage-accent);
color: var(--stage-accent-ink);
font: 500 var(--text-base) / 1 var(--font-body);
white-space: nowrap;
cursor: pointer;
transition: background-color var(--dur-micro) var(--ease-out);
}
.button:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 3px;
}
.ripple {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
background: currentColor;
opacity: 0.25;
translate: -50% -50%;
pointer-events: none;
animation: ripple 600ms var(--ease-out) forwards;
}
@keyframes ripple {
from { transform: scale(0); opacity: 0.25; }
to { transform: scale(24); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.ripple { display: none; }
.button:active { background: var(--stage-accent-hover); }
}