Scroll-Snap Rotator:「hello ___」滚动轮播
一个经典的滚动轮播效果(出处见 CodePen,思路来自 @jh3yy):左边 hello 钉住不动,右边一列名字竖向滚动并吸附到正中,居中那个放大变亮、其余缩小变暗——像不断切换的
hello, ___。原作用 GSAP ScrollTrigger(pin + scrub)做高亮,而这套效果的三块拼图全部是纯 CSS,无需任何 JS;其中高亮部分用到 View Timeline 页讲的 animation-timeline: view()(见
caniuse)。
1 · 三块拼图:把 GSAP 的职责拆给原生 CSS
原作把「钉住 + 吸附 + 跟随滚动高亮」都交给 GSAP,而这三件事各有一条纯 CSS 的对应写法:
| 拼图 | 原生 CSS | 怎么拆 |
|---|---|---|
| 吸附 | scroll-snap-align: center |
给滚动容器 scroll-snap-type: y mandatory,每个 li 设 scroll-snap-align: center,松手即吸附到容器正中。这一段原作本来就是纯 CSS,GSAP 未介入。 |
| 钉住 hello | position: sticky; top: 50% |
原作的 pin: true 等价于一句 position: sticky; top: 50%:列表比容器高,hello 就一路粘在视口中线。 |
| 居中高亮 | animation-timeline: view() |
原作那条 scrub 时间线,换成 scroll-driven animation:每个名字按自身相对容器视口的位置(view progress)插值——走到正中(进度 50%)时 opacity:1 / scale:1,两端 0.2 / 0.8。时间轴 = 滚动,天然可回退。 |
2 · 现场效果
在下面的框里上下滚动(触摸板 / 滚轮 / 拖滚动条都行)。虚线是容器正中,名字滚到那条线上时被「点亮」,顶部读数会同步显示当前居中的词:
3 · 关键 CSS
.scroller {
overflow-y: scroll;
scroll-snap-type: y mandatory;
}
.greet {
position: sticky;
top: 50%;
transform: translateY(-50%);
}
li { scroll-snap-align: center; }
@keyframes spotlight {
0%, 100% { opacity: .18; scale: .8; }
50% { opacity: 1; scale: 1; }
}
li > span {
transform-origin: left center;
animation: spotlight linear both;
animation-timeline: view();
}
为什么 snap 中心和高亮峰值刚好对齐? scroll-snap-align: center 把 li 停在滚动容器正中,而 view() 的进度 50% 也正是「元素中心 = 容器视口中心」那一刻——两个 50% 天然重合,所以吸附到位时高亮恰好满格。列表首尾各垫了半个容器高的空白(::before /
::after),否则第一个和最后一个名字滚不到正中。
fill 用 both 不是 backwards: 名字在进入高亮区间之前(上方)要保持 0% 帧的暗态,离开之后(下方)也要保持 100% 帧的暗态——两端都得撑住,所以用 both。对比
view-timeline 页的 spring-box:那是「进来才动、之后无所谓」,用 backwards 即可。
对照原作: GSAP 版用 ScrollTrigger { pin:true, scrub:true } 钉住 + 把时间线绑到滚动;纯 CSS 把它拆成 position: sticky(钉)+ animation-timeline: view()(绑滚动)。代价只有兼容性:view() 需要 Chromium 115+ / Firefox(带
flag),老浏览器靠 @supports 降级成「不高亮但能吸附」的普通列表;要全平台覆盖就保留 GSAP。