居中聚焦轮播
左右滑动一排会吸附到中心的卡片,位于中间的卡片放大到完整尺寸,其余的向后退。
01黎明
02正午
03黄昏
04深夜
05风暴
用原生滚动吸附和 CSS 滚动驱动动画制作居中聚焦的卡片轮播。轨道是一个横向 flex 行,设置 overflow-x: auto、scroll-snap-type: x mandatory,左右内边距为“轨道宽度的一半减去卡片宽度的一半”,让第一张和最后一张卡片也能到达中心。每张卡片设置 scroll-snap-align: center。用 animation-timeline: view(inline) 为每张卡片驱动 keyframe:0% 时 scale(0.8)、opacity 0.45,50% 时 scale(1)、opacity 1,100% 时回到 scale(0.8)、opacity 0.45,这样位于中心的卡片最大。把动画包在 @supports (animation-timeline: view()) 中,其他浏览器得到普通的吸附滚动行。视觉上隐藏滚动条,但保持轨道可通过键盘聚焦。在 prefers-reduced-motion 下去掉缩放,保留吸附。卡片文字为黎明、正午、黄昏、深夜、风暴。
源代码
'use client'
import s from './demo.module.css'
export default function SnapCarousel({ labels }: { labels: Record<string, string> }) {
return (
<div className={s.wrap}>
<div className={s.track} tabIndex={0} role="region" aria-label={labels.carousel}>
{labels.items.split('|').map((t, i) => (
<div key={t} className={s.card} style={{ '--h': 30 + i * 60 } as React.CSSProperties}>
<span className={s.n}>{String(i + 1).padStart(2, '0')}</span>
<strong className={s.name}>{t}</strong>
</div>
))}
</div>
<span className={s.hint}>{labels.hint}</span>
</div>
)
}
.wrap {
position: absolute;
inset: 0;
display: grid;
align-content: center;
gap: 10px;
}
.track {
--card: 120px;
display: flex;
gap: 12px;
padding: 8px calc(50% - var(--card) / 2);
overflow-x: auto;
scroll-snap-type: x mandatory;
overscroll-behavior-x: contain;
scrollbar-width: none;
}
.track::-webkit-scrollbar { display: none; }
.track:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }
.card {
flex: none;
display: grid;
align-content: space-between;
width: var(--card);
height: 150px;
padding: 12px;
border-radius: 14px;
background: linear-gradient(160deg, oklch(64% 0.1 var(--h)), oklch(36% 0.06 var(--h)));
scroll-snap-align: center;
}
.n { font: 500 var(--text-xs) / 1 var(--font-mono); color: oklch(100% 0 0 / 0.7); }
.name { font: 700 var(--text-xl) / 1 var(--font-display); color: oklch(98% 0.01 80); }
.hint {
font: 500 var(--text-xs) / 1 var(--font-mono);
letter-spacing: 0.06em;
text-transform: uppercase;
text-align: center;
color: var(--stage-muted);
}
@supports (animation-timeline: view()) {
.card {
animation: focus linear both;
animation-timeline: view(inline);
}
}
@keyframes focus {
0%, 100% { transform: scale(0.8); opacity: 0.45; }
50% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.card { animation: none; }
}