传统网页滚动动画依赖JavaScript监听scroll事件,不仅性能开销大,而且难以实现精细控制。CSS 滚动驱动动画(Scroll-driven Animations)和视图过渡API(View Transitions API)带来了原生解决方案。本文通过构建一个完整的“产品故事”滚动页面,展示如何用纯CSS实现视差、渐入、逐帧动画以及平滑的页面过渡。
一、滚动驱动动画基础
滚动驱动动画允许动画进度与滚动位置绑定。核心是scroll-timeline(已更新为animation-timeline: scroll())和view-timeline。
/* 基本用法:元素滚动到视口时触发动画 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
.animated-element {
animation: fadeIn 1s linear;
animation-timeline: view(); /* 基于元素自身在视口中的可见性 */
animation-range: entry 0% entry 100%; /* 从进入视口开始到完全进入 */
}
view()时间线基于元素在视口中的位置,scroll()基于滚动容器的滚动位置。通过animation-range可以控制动画的触发区间。
二、视图过渡API:SPA般流畅的页面切换
视图过渡API允许在不同页面状态之间创建平滑的动画,常用于多页应用(MPA)或单页应用(SPA)的导航。通过document.startViewTransition()触发。
// 触发视图过渡
document.startViewTransition(() => {
// DOM更新
document.getElementById('content').innerHTML = newContent;
});
CSS中可以使用伪元素::view-transition-old和::view-transition-new自定义过渡效果。
三、完整案例:产品故事滚动页面
我们将构建一个页面,包含多个章节,每个章节在滚动时触发动画:标题从左侧滑入、图片缩放、背景色变化。同时,页面切换(如点击导航)使用视图过渡API。
HTML结构
<nav id="nav">
<a href="#chapter1" rel="external nofollow" onclick="navigateTo('chapter1')">发现</a>
<a href="#chapter2" rel="external nofollow" onclick="navigateTo('chapter2')">创新</a>
<a href="#chapter3" rel="external nofollow" onclick="navigateTo('chapter3')">未来</a>
</nav>
<main>
<section id="chapter1" class="chapter" data-color="#1a1a2e">
<div class="content">
<h2 class="title">发现未知</h2>
<p class="desc">每一段旅程都始于一次勇敢的探索。</p>
<img src="https://picsum.photos/800/400?random=1" alt="探索" class="chapter-image" />
</div>
</section>
<section id="chapter2" class="chapter" data-color="#16213e">
<div class="content">
<h2 class="title">创新突破</h2>
<p class="desc">用技术重新定义可能性。</p>
<img src="https://picsum.photos/800/400?random=2" alt="创新" class="chapter-image" />
</div>
</section>
<section id="chapter3" class="chapter" data-color="#0f3460">
<div class="content">
<h2 class="title">共创未来</h2>
<p class="desc">携手迈向更智能的明天。</p>
<img src="https://picsum.photos/800/400?random=3" alt="未来" class="chapter-image" />
</div>
</section>
</main>
CSS实现(滚动驱动动画 + 视图过渡)
/* 基础重置与布局 */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #0a0a23;
color: #fff;
overflow-x: hidden;
}
nav {
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
gap: 2rem;
padding: 1.5rem;
background: rgba(10, 10, 35, 0.8);
backdrop-filter: blur(10px);
z-index: 100;
}
nav a {
color: #ccc;
text-decoration: none;
font-size: 1.1rem;
transition: color 0.3s;
cursor: pointer;
}
nav a:hover { color: #fff; }
/* 章节布局 */
.chapter {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 6rem 2rem;
scroll-snap-align: start;
}
main {
scroll-snap-type: y mandatory;
overflow-y: scroll;
height: 100vh;
}
.content {
max-width: 900px;
margin: 0 auto;
text-align: center;
}
/* ===== 滚动驱动动画 ===== */
/* 标题动画:从左侧滑入 */
.title {
font-size: 4rem;
font-weight: 800;
opacity: 0;
transform: translateX(-100px);
animation: slideInTitle 1s ease-out;
animation-timeline: view();
animation-range: entry 0% entry 80%; /* 进入视口80%时完成 */
}
@keyframes slideInTitle {
from {
opacity: 0;
transform: translateX(-100px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* 描述文字动画:淡入并上移 */
.desc {
font-size: 1.3rem;
margin: 1.5rem 0;
opacity: 0;
transform: translateY(30px);
animation: fadeUpDesc 0.8s ease-out;
animation-timeline: view();
animation-range: entry 10% entry 70%;
}
@keyframes fadeUpDesc {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 图片动画:缩放与旋转 */
.chapter-image {
width: 100%;
max-width: 700px;
border-radius: 16px;
margin-top: 2rem;
opacity: 0;
scale: 0.8;
animation: zoomImage 1.2s ease-out;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
@keyframes zoomImage {
from {
opacity: 0;
scale: 0.8;
rotate: -5deg;
}
to {
opacity: 1;
scale: 1;
rotate: 0deg;
}
}
/* 背景色过渡(基于滚动位置) */
body {
background: #0a0a23;
transition: background 0.5s ease;
}
/* 当每个章节进入视口时,通过JS改变body背景色(见下方脚本) */
/* 这里用CSS实现不了动态背景跟随,但可以用JS辅助 */
/* ===== 视图过渡API样式 ===== */
::view-transition-old(root) {
animation: fadeOut 0.5s ease-out;
}
::view-transition-new(root) {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
JavaScript辅助:导航切换与背景色
// 视图过渡导航
function navigateTo(chapterId) {
if (!document.startViewTransition) {
// 降级:直接跳转
document.getElementById(chapterId).scrollIntoView({ behavior: 'smooth' });
return;
}
// 使用视图过渡API
document.startViewTransition(() => {
document.getElementById(chapterId).scrollIntoView({ behavior: 'instant' });
});
}
// 背景色随章节变化
const chapters = document.querySelectorAll('.chapter');
const body = document.body;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const color = entry.target.dataset.color;
body.style.background = color;
}
});
}, { threshold: 0.5 });
chapters.forEach(ch => observer.observe(ch));
// 初始背景色
if (chapters.length > 0) {
body.style.background = chapters[0].dataset.color;
}
四、核心机制解析
1. 滚动驱动动画(Scroll-driven Animations)
使用animation-timeline: view(),每个元素的动画进度由其与视口的交集决定。animation-range定义了动画开始和结束的触发点。例如entry 0% entry 80%表示元素从刚进入视口到进入80%时完成动画。
这完全避免了JavaScript滚动监听,性能优异且声明式。
2. 视图过渡API(View Transitions API)
当用户点击导航链接时,document.startViewTransition()会捕获当前页面状态,执行DOM更新,然后平滑过渡到新状态。CSS伪元素::view-transition-old和::view-transition-new允许自定义过渡动画。本例中使用了淡入淡出效果。
注意:视图过渡API需要同源页面,且目前仅Chrome 111+支持。本案例中作为渐进增强使用。
3. 滚动捕捉(Scroll Snap)
在main元素上设置scroll-snap-type: y mandatory,每个章节scroll-snap-align: start,实现滚动时自动对齐到章节顶部,增强叙事节奏感。
五、运行与测试
将完整代码保存为scroll-story.html,在Chrome 115+中打开。滚动页面,观察:
- 每个章节的标题从左侧滑入,图片缩放旋转
- 背景色随章节自动变化
- 点击导航链接,页面以淡入淡出效果过渡
六、浏览器兼容性与降级
滚动驱动动画(animation-timeline: view())在Chrome 115+、Edge 115+中支持。Firefox和Safari目前不支持。降级方案:
- 使用
@supports (animation-timeline: view())检测,不支持的浏览器使用Intersection Observer + CSS class回退。 - 视图过渡API仅Chrome支持,其他浏览器忽略即可,不影响基本功能。
@supports (animation-timeline: view()) {
.title { animation-timeline: view(); }
}
七、总结
通过滚动驱动动画和视图过渡API,我们用纯CSS+少量JS实现了以往需要复杂JavaScript库才能完成的滚动叙事效果。这不仅降低了代码复杂度,还提升了性能——动画在合成线程中运行,不阻塞主线程。
随着浏览器支持的普及,这些API将彻底改变我们构建交互式网页的方式。现在就开始尝试,让你的页面在滚动中“活”起来。
本文为原创技术教程,代码基于Chrome 115+测试。建议结合Can I Use进行兼容性处理。

