滚动文字按钮
悬停时按钮文字逐字向上滚走,被下方的副本替换,从左到右形成一道快速的波浪。
用纯 CSS 配合 React 拆字,制作悬停时文字逐字滚动的按钮。把文字拆成每个字一个 span(空格保留为不换行空格),每个 span 设置 text-shadow: 0 1.3em 0 currentColor,在下方一行画出完全相同的副本。把这些字包在一个 inline-flex、高度 1.3em、overflow: hidden 的 span 中,只露出一行,每个字的行高为 1.3em。在 :hover 和 :focus-visible 时,所有字在 400ms 内移到 translateY(-1.3em),缓动 cubic-bezier(0.16, 1, 0.3, 1),并通过 CSS 变量把 transition-delay 设为 序号 × 25ms,让滚动从左到右扫过。完整文字放在 aria-label 中,拆分出的 span 设为 aria-hidden。在 prefers-reduced-motion 下文字保持不动,只改变背景色。按钮文字为“联系我们”。
源代码
'use client'
import s from './demo.module.css'
export default function RollingText({ labels }: { labels: Record<string, string> }) {
return (
<button type="button" className={s.button} aria-label={labels.cta}>
<span className={s.label}>
{Array.from(labels.cta).map((c, i) => (
<span key={i} className={s.char} style={{ '--i': i } as React.CSSProperties} aria-hidden="true">
{c === ' ' ? ' ' : c}
</span>
))}
</span>
</button>
)
}
.button {
display: inline-flex;
align-items: center;
height: 52px;
padding: 0 30px;
border: 0;
border-radius: 999px;
background: var(--stage-ink);
color: var(--stage-bg);
font: 600 var(--text-base) / 1.3 var(--font-body);
white-space: nowrap;
cursor: pointer;
transition: background-color var(--dur-short);
}
.button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }
/* clip to exactly one line so the shadow copy below stays hidden */
.label {
display: inline-flex;
height: 1.3em;
overflow: hidden;
}
.char {
display: inline-block;
line-height: 1.3em;
text-shadow: 0 1.3em 0 currentColor;
transition: transform 400ms var(--ease-out);
transition-delay: calc(var(--i) * 25ms);
}
.button:hover .char,
.button:focus-visible .char { transform: translateY(-1.3em); }
@media (prefers-reduced-motion: reduce) {
.char { transition: none; }
.button:hover .char, .button:focus-visible .char { transform: none; }
.button:hover { background: var(--stage-accent); }
}