无限跑马灯
两排标签朝相反方向无缝滑动,悬停时暂停。
- 品牌设计
- 网页设计
- 动效
- Next.js
- 文案
- SEO
- 电商
- 数据看板
- 原型
- 落地页
- 设计系统
用 CSS 在 React 中制作两排反向移动的无缝跑马灯。每一排在同一个 flex 轨道中把项目列表渲染两遍(第二份加 aria-hidden),并让轨道从 translateX(0) 线性无限动画到 translateX(-50%)(第一排 30 秒,第二排 36 秒反向),这样循环时不会跳动。轨道使用 gap,并设置与 gap 相同的 padding-right,让两半精确对齐。用 mask-image: linear-gradient(90deg, transparent, black 10%, black 90%, transparent) 淡化两侧边缘。悬停和 focus-within 时用 animation-play-state: paused 暂停动画。项目为圆角标签。在 prefers-reduced-motion 下停止动画,改为允许横向滚动。
源代码
'use client'
import s from './demo.module.css'
function Row({ items, reverse }: { items: string[]; reverse?: boolean }) {
return (
<div className={s.row}>
<div className={reverse ? `${s.track} ${s.reverse}` : s.track}>
{[0, 1].map((copy) => (
<ul key={copy} className={s.list} aria-hidden={copy === 1 || undefined}>
{items.map((t) => (
<li key={t} className={s.tag}>
{t}
</li>
))}
</ul>
))}
</div>
</div>
)
}
export default function Marquee({ labels }: { labels: Record<string, string> }) {
return (
<div className={s.wrap}>
<Row items={labels.row1.split('|')} />
<Row items={labels.row2.split('|')} reverse />
</div>
)
}
.wrap { display: grid; gap: 12px; width: 100%; }
.row {
overflow: hidden;
mask-image: linear-gradient(90deg, transparent, black 10%, black 90%, transparent);
}
.track {
display: flex;
width: max-content;
animation: slide 30s linear infinite;
}
.reverse { animation-duration: 36s; animation-direction: reverse; }
.row:hover .track, .row:focus-within .track { animation-play-state: paused; }
.list {
display: flex;
gap: 10px;
margin: 0;
padding: 0 10px 0 0;
list-style: none;
}
.tag {
padding: 8px 14px;
border: 1px solid var(--stage-rule);
border-radius: 999px;
background: var(--stage-surface);
color: var(--stage-ink);
font: 500 var(--text-sm) / 1 var(--font-body);
white-space: nowrap;
}
.reverse .tag:nth-child(odd) { border-color: var(--stage-accent); color: var(--stage-accent); }
@keyframes slide { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
.track { animation: none; }
.row { overflow-x: auto; }
}