90% 浏览器都支持了,你的懒加载还在手写 IntersectionObserver
全球约 90% 的浏览器已经支持 CSS scroll-driven animations,但绝大多数项目的懒加载和入场动画还在手写 IntersectionObserver——observe、回调里 classList.add、CSS transition,三件套缺一个就炸。其实 view() 加 animation-range 这两行 CSS 就能把整件事干完。
IntersectionObserver 能做到的事,CSS 现在都能做
IntersectionObserver 做的事情本质上就三件:监听元素进入视口、监听元素离开视口、拿到元素在视口中的比例。CSS scroll-driven animations 的 view() timeline + animation-range 组合把这三件事全包了:
/* 入场动画:元素进入视口 0%~40% 时播放 */
@keyframes fade-up {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: none; }
}
.reveal {
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
/* 离场动画:元素离开视口时播放 */
@keyframes slide-out {
to { opacity: 0; transform: translateY(32px); }
}
.exit {
animation: slide-out linear both;
animation-timeline: view();
animation-range: exit 0% cover 20%;
}
animation-range 是关键参数,它决定了 keyframes 的 0% 到 100% 分别映射到滚动进度的哪个区间。entry/cover/exit 是三个基准线:
entry 0%:元素刚进入视口那一刻cover 40%:元素覆盖视口 40% 的那一刻exit 0%:元素刚开始离开视口的那一刻
有了这些精确的锚点,进度条、目录高亮、parallax 效果全都可以用纯 CSS 写。
比 rAF scroll listener 强在哪
以前写滚动效果的标准做法是监听 scroll 事件然后用 requestAnimationFrame:
// 旧写法:主线程上跑,每帧都要算
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) e.target.classList.add('visible');
});
}, { threshold: 0.4 });
observer.observe(el);
CSS scroll-driven animations 跑在浏览器的合成线程上,不占主线程。即使用户狂滚 60fps,动画也不会卡,因为进度值由浏览器直接计算,不经过 JS 回调。这意味着:
- 不需要
will-change: transform来提示浏览器 - 不需要
passive: true来声明不 preventDefault - 不需要
disconnect()来清理 observer
浏览器支持情况:约 90%
Chrome 115+(2023 年 7 月)、Edge 115+、Safari 18+(2025 年 9 月)已全部稳定。Firefox 126+ 稳定版也已支持,Firefox Nightly 默认开启。全球约 90% 覆盖,生产环境可以直接用:
/* 渐进增强写法 */
.reveal {
opacity: 0;
transform: translateY(24px);
}
@supports (animation-timeline: view()) {
.reveal {
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
}
不支持的浏览器会看到 opacity: 0 的初始状态,内容仍然可见,只是没有动画效果。配合 prefers-reduced-motion 媒体查询使用:
@media (prefers-reduced-motion: reduce) {
.reveal {
animation: none;
opacity: 1;
transform: none;
}
}
下一步
下次做懒加载或入场动画时,先写 @supports (animation-timeline: view()) 里的 CSS 方案,再在 @supports 外面写静态 fallback。只要设计允许静态展示,你就可以把整个 IntersectionObserver + classList 组合删掉。
评论区
登录后可评论。