CSS 滚动驱动动画:@scroll-timeline 实现视差效果
@scroll-timeline 这个 at-rule 已经被 CSS 规范废弃,现在实现视差效果的正确写法是用 `animation-timeline` 属性把动画挂到滚动时间线上,配合 `scroll()` 或 `view()` 两条内置时间线,几行 CSS 就能做出跟随滚动、不同速率的视差层,不需要一行 JS。
先搞清楚:@scroll-timeline 已经不能用了
结论:`@scroll-timeline` 是 Scroll-driven Animations 规范的早期草案语法,现已被删掉,改由 `scroll-timeline-name`、`scroll-timeline-axis`、`animation-timeline`、`timeline-scope` 这一组属性替代。你在老教程里看到的写法,在 Chrome/Edge 115+ 里直接不生效。
所以遇到"照着写没反应"的情况,第一件事是把 `@scroll-timeline` 整块删掉,换成属性写法。浏览器支持方面,Chromium 115+ 默认开启,Firefox 与 Safari 在 2025 年下半年陆续默认支持,写代码时用 `@supports (animation-timeline: scroll())` 包一层做渐进增强最稳妥。
视差的最小可用代码
结论:视差层的最短写法是 `animation-timeline: scroll(root block)`,不用给任何元素命名,直接把动画绑到页面根滚动条上;不同元素给不同的位移量,速率差就是视差。
/* 背景层:移动幅度大 */
.parallax-bg {
animation: bg-move linear both;
animation-timeline: scroll(root block);
}
@keyframes bg-move {
from { transform: translateY(0) scale(1.1); }
to { transform: translateY(-25vh) scale(1.1); }
}
/* 前景层:移动幅度小,两层错开就产生了纵深 */
.parallax-fg {
animation: fg-move linear both;
animation-timeline: scroll(root block);
}
@keyframes fg-move {
from { transform: translateY(0); }
to { transform: translateY(-8vh); }
}
三个要点:一是 `scroll(root block)` 里 `root` 指根滚动容器,`block` 指纵向轴,滚动容器不是根元素时写成 `scroll(nearest block)` 或直接写容器名;二是用滚动时间线时 `animation-duration` 会被忽略,进度完全由滚动位置决定;三是务必保留 `animation-fill-mode: both`,否则动画区间外元素会弹回初始位置。
用 view() 做"入场视差"更省事
结论:如果视差只发生在元素进入视口的阶段,用 `view()` 时间线比 `scroll()` 更省心,它自动以"元素与视口的交叉关系"为进度,还能用 `animation-range` 精确切区间。
.card {
animation: rise linear both;
animation-timeline: view();
animation-range: entry 0% cover 45%;
}
@keyframes rise {
from { opacity: 0; transform: translateY(60px) scale(.96); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
`animation-range` 支持的区间关键字有 `cover`、`contain`、`entry`、`exit`,`entry 0%` 表示元素刚碰到视口下沿,`cover 45%` 表示元素被视口覆盖到 45% 处。想做出"一边滚一边慢慢浮起"的视差感,把区间拉长(比如 `cover 0% cover 100%`)就行。`view()` 默认作用域是元素自身,不用额外声明。
两个必须避开的坑
结论:视差动画只能碰 `transform`、`opacity`、`filter` 这类合成属性,一旦动画 `top`、`margin`、`width`,滚动时会疯狂触发 layout,帧率直接崩。
第一个坑是时间线作用域。用命名时间线时,名字默认只在声明元素的子树里可见;如果动画元素和时间线元素是"兄弟关系",需要在共同祖先上加 `timeline-scope: --my-scroll;` 把它提升上去,否则 `animation-timeline: --my-scroll` 会静默失效。
第二个坑是无障碍。滚动驱动动画同样会前庭敏感人群不适,老老实实加一段:
@media (prefers-reduced-motion: reduce) {
.parallax-bg, .parallax-fg { animation: none; }
}
降级方案别偷懒
结论:不支持的浏览器不会报错,只会停在动画第一帧,所以必须用 `@supports` 给一个静态可用的默认样式——比如背景层直接给 `transform: scale(1.1)` 做裁剪填充,而不是留一片空白。
.parallax-fg { /* 默认样式:可读、可用 */ }
@supports (animation-timeline: scroll()) {
.parallax-fg { animation: fg-move linear both; animation-timeline: scroll(root block); }
}
如果项目必须覆盖老浏览器,再用 `IntersectionObserver` + `requestAnimationFrame` 走 JS 兜底,但记得只在 `@supports` 判定失败时才加载这段脚本,别让新浏览器白白多跑一份监听。
一句话收束:把 `@scroll-timeline` 换成 `animation-timeline: scroll()` 或 `view()`,只动画 transform、用 `animation-range` 控区间、用 `@supports` 兜底,视差效果十几行 CSS 就能落地,且比 JS 滚动监听更省电更顺滑。
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员





