scroll-driven animations(滚动驱动动画)目前不适用于高性能滚动动画,因chrome 115+/safari 17.4+中易触发强制同步布局;仅适用于静态布局、预知范围、无js干预的简单动效,如固定高度容器内平移、顶部banner渐显、纯css分段背景色切换;关键前提是timeline源为不重排容器(如height:100vh的),目标元素脱离文档流(position:absolute或transform隔离);典型错误包括绑定body/html、用selector()动态定位、触发布局属性、多timeline重叠;高性能方案应采用intersectionobserver+transform+will-change组合,监听进入/离开视口粗粒度时机,动效全用transform/opacity走合成层,并缓存getboundingclientrect结果,避免主线程布局计算。

Scroll-driven Animations(滚动驱动动画)目前不能直接用于高性能滚动动画——它在 Chrome 115+ 和 Safari 17.4+ 中可用,但存在严重性能陷阱:浏览器必须在每次滚动帧中计算 scroll-timeline 进度,若绑定元素频繁重排、或 timeline 范围跨 viewport 太大,极易触发强制同步布局(Layout Thrashing),反而比 requestAnimationFrame + getBoundingClientRect() 更卡。
哪些场景下 scroll-timeline 真的能用
只适用于「静态布局 + 预知范围 + 无 JS 干预」的简单动效,比如:
- 固定高度容器内,单个子元素从右进左出的平移(
translateX) - 页面顶部 banner 图片随滚动渐显(
opacity从 0→1) - 纯 CSS 控制的分段背景色切换(不依赖 DOM 尺寸测量)
关键前提是:timeline 的 source 是一个不会因内容变化而重排的容器(如 height: 100vh 的 <section></section>),且目标元素不参与文档流(position: absolute 或 transform 隔离)。
scroll-timeline 性能崩坏的典型错误
以下写法会立刻拖慢滚动帧率:
- 把
scroll-timeline绑定到body或html—— 滚动源尺寸不可控,浏览器无法缓存 layout 信息 - timeline 的
start/end使用selector()动态定位元素(如selector(#trigger) 0%),每次滚动都触发样式查询 + 布局计算 - 动画属性包含
width、height、left/top等触发布局的属性 - 同时启用多个
scroll-timeline且范围重叠(浏览器无法合并 timeline 更新)
错误示例:
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
<p>.section {
scroll-timeline: --tl vertical;
}</p><p>@scroll-timeline --tl {
source: selector(body); /<em> ❌ body 不是稳定滚动源 </em>/
start: 0%;
end: 100%;
}</p><p>.fade-target {
animation: fade-in 1s linear;
animation-timeline: --tl;
}</p>
真正高性能的滚动动画该怎么做
用 IntersectionObserver + transform + will-change 组合,避开主线程布局计算:
- 只监听元素进入/离开视口的粗粒度时机(
threshold: [0, 0.2, 0.5, 0.8, 1]),不逐帧读取scrollTop - 动效全部用
transform和opacity,确保走合成层(Compositor Layer) - 对动画元素提前声明
will-change: transform, opacity(仅对少量元素,避免内存浪费) - 必要时用
passive: true的addEventListener('scroll', ...)+requestAnimationFrame做细粒度控制,但必须配合el.getBoundingClientRect()缓存结果,禁止在回调里调用getComputedStyle
例如实现“滚动中元素缩放”:
.anim-item {
transform: scale(0.8);
opacity: 0.6;
will-change: transform, opacity;
}
<p>.anim-item.in-view {
transform: scale(1);
opacity: 1;
transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1),
opacity 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
}</p>再用 IntersectionObserver 切换 in-view 类名,而非靠 timeline 驱动。滚动动画的性能瓶颈从来不在“怎么动”,而在“怎么知道该动了”。scroll-timeline 把感知逻辑交给了渲染引擎,但当前实现缺乏 layout 缓存和批量更新机制——这意味着你越想精确,它越卡。真要逐像素响应,老老实实用 IntersectionObserver 或 requestAnimationFrame 手动采样,反而更可控。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











