滚动链接动画通常能为网站增添一抹亮色,但长期以来一直被 JavaScript 所主导。现在,一项全新的规范正在实施,使我们能够使用 CSS 创建丰富的滚动驱动体验!
当我们想到滚动驱动动画时,我们通常会想到两种情况:
- 动画在用户滚动时发生,动画的进度与滚动进度明确关联。例如,长文章的进度条。
- 当元素进入、退出或在可视区域内移动时发生的动画——通常是视口,但也可能是另一个可滚动容器的可见部分(这被定义为滚动视口)。
《滚动驱动动画规范》涵盖了这两种类型的动画。在本文中,我们将首先看看滚动进度时间线,顾名思义,它将动画与滚动进度关联起来。
在这个例子中,我们将实现一个常见功能:当用户滚动网页时,一个简单的进度条会从左到右缩放。因为我们希望将动画与根滚动器的进度关联起来,所以我们可以使用一个匿名的滚动进度时间线。
首先,让我们定义动画本身。我们希望进度条从左到右缩放,所以我们将使用transform。
@keyframes scaleProgress {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
为了将我们的进度条元素的动画与滚动进度关联起来,我们使用了animation-timeline 属性,并将scroll() 函数作为其值。
.progress {
animation-timeline: scroll();
}
scroll() 函数允许我们指定滚动容器和轴。默认值是 scroll(nearest block),这意味着动画将与块轴上最近的可滚动祖先元素关联。这足以满足我们的目的,尽管我们可以选择性地将根元素指定为滚动容器,因为我们希望明确地将动画与视口的滚动进度关联起来。
.progress {
animation-timeline: scroll(root block);
}
最后,我们需要将动画添加到进度条元素上,并将我们的关键帧动画作为animation-name。我们需要将动画持续时间设置为 auto,因为持续时间将由滚动进度决定。我们还将缓动(animation-timing-function)设置为 linear,以便它与滚动同步平滑进行。如果我们使用默认值(ease),动画将开始缓慢,然后迅速加速,最后在结束时减速——这与我们想要的进度指示器不符!
.progress {
animation-timeline: scroll(root);
animation-name: scaleProgress;
animation-duration: auto;
animation-timing-function: linear;
}
我们可以使用 animation 简写属性来稍微简化这一点。
.progress {
animation: scaleProgress auto linear;
animation-timeline: scroll(root);
}
注意: animation-timeline 目前不包含在简写属性中。但是,animation 属性会将 animation-timeline 重置为 auto(默认值),因此我们需要在 animation 简写属性之后声明 animation-timeline。
* {
box-sizing: border-box;
}
body {
font-family: "Helvetica", sans-serif;
line-height: 1.6;
min-height: 300vh;
margin: 0;
font-size: clamp(1rem, 1rem + 1vw, 1.5rem);
}
h1 {
line-height: 1.25;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: clamp(1rem, 2vw, 5rem);
}
.progress {
height: 1rem;
background: blue;
position: fixed;
top: 0;
left: 0;
width: 100%;
transform-origin: 0 50%;
animation: scaleProgress auto linear;
animation-timeline: scroll(root);
}
@keyframes scaleProgress {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
<div class="progress"></div>
<div class="container">
<h1>Anonymous scroll timeline</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Commodo viverra maecenas
accumsan lacus. Orci sagittis eu volutpat odio facilisis mauris. Eu nisl
nunc mi ipsum faucibus vitae aliquet nec. Amet nisl purus in mollis nunc
sed. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam.
Lorem sed risus ultricies tristique nulla. Commodo sed egestas egestas
fringilla phasellus faucibus. Semper eget duis at tellus at urna condimentum
mattis pellentesque. Porta lorem mollis aliquam ut porttitor leo a diam. At
lectus urna duis convallis convallis tellus id interdum velit. Placerat orci
nulla pellentesque dignissim enim sit amet venenatis urna. Rutrum tellus
pellentesque eu tincidunt tortor. Nulla facilisi cras fermentum odio eu
feugiat. Aliquet risus feugiat in ante metus. Quis imperdiet massa tincidunt
nunc pulvinar sapien et. Vel pharetra vel turpis nunc.
</p>
<p>
Potenti nullam ac tortor vitae purus. Tempor orci dapibus ultrices in
iaculis nunc sed augue. Adipiscing elit duis tristique sollicitudin nibh.
Luctus accumsan tortor posuere ac ut consequat semper. Enim nulla aliquet
porttitor lacus. Netus et malesuada fames ac. Aliquam ultrices sagittis orci
a scelerisque. Fringilla phasellus faucibus scelerisque eleifend donec
pretium vulputate sapien. Nibh praesent tristique magna sit amet purus
gravida quis. Mi proin sed libero enim sed faucibus turpis in eu. Natoque
penatibus et magnis dis parturient montes nascetur ridiculus. Pellentesque
elit ullamcorper dignissim cras tincidunt lobortis. Nunc faucibus a
pellentesque sit amet porttitor eget dolor. Luctus accumsan tortor posuere
ac ut. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper
velit. Ac odio tempor orci dapibus ultrices in iaculis nunc sed.
</p>
<p>
Molestie ac feugiat sed lectus vestibulum mattis. Elementum curabitur vitae
nunc sed velit dignissim sodales ut. Netus et malesuada fames ac turpis
egestas sed tempus. Viverra nam libero justo laoreet sit amet cursus sit
amet. Maecenas sed enim ut sem viverra aliquet eget. Et netus et malesuada
fames ac turpis egestas maecenas pharetra. Imperdiet proin fermentum leo vel
orci porta. Nunc eget lorem dolor sed viverra ipsum nunc aliquet. Facilisis
mauris sit amet massa vitae. Cras semper auctor neque vitae. Adipiscing diam
donec adipiscing tristique risus. Scelerisque eu ultrices vitae auctor eu.
Adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus urna. Egestas
quis ipsum suspendisse ultrices gravida. Semper quis lectus nulla at
volutpat diam. Egestas congue quisque egestas diam in arcu.
</p>
<p>
Est velit egestas dui id ornare arcu odio ut sem. Tortor consequat id porta
nibh venenatis. Proin sagittis nisl rhoncus mattis rhoncus urna neque. Porta
non pulvinar neque laoreet suspendisse interdum. Lacus vel facilisis
volutpat est velit egestas dui. Facilisi morbi tempus iaculis urna id
volutpat. Venenatis urna cursus eget nunc scelerisque viverra. Ultrices
gravida dictum fusce ut. Eu augue ut lectus arcu. Orci dapibus ultrices in
iaculis. Rhoncus mattis rhoncus urna neque viverra justo nec ultrices. Odio
eu feugiat pretium nibh ipsum consequat. Accumsan in nisl nisi scelerisque
eu ultrices vitae. Nunc faucibus a pellentesque sit. Ultricies integer quis
auctor elit sed vulputate mi. Nulla aliquet enim tortor at auctor urna nunc
id cursus.
</p>
<p>
Integer enim neque volutpat ac tincidunt vitae semper. Condimentum lacinia
quis vel eros donec ac odio tempor orci. Imperdiet dui accumsan sit amet
nulla facilisi morbi tempus. Suspendisse potenti nullam ac tortor vitae. Non
sodales neque sodales ut. Elementum eu facilisis sed odio. Aliquet nec
ullamcorper sit amet risus nullam eget felis eget. Diam phasellus vestibulum
lorem sed risus ultricies tristique. Facilisis sed odio morbi quis. Diam
quis enim lobortis scelerisque fermentum dui faucibus. Ullamcorper dignissim
cras tincidunt lobortis feugiat vivamus at augue eget. Platea dictumst
vestibulum rhoncus est pellentesque elit ullamcorper dignissim.
</p>
</div>
就像常规的关键帧动画一样,我们可以同时应用多个滚动时间线动画,例如改变进度条的颜色。
.progress {
animation:
scaleProgress auto linear,
colorChange auto linear;
animation-timeline: scroll(root);
}
@keyframes scaleProgress {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
@keyframes colorChange {
0% {
background-color: red;
}
50% {
background-color: yellow;
}
100% {
background-color: lime;
}
}
<div class="progress"></div>
<div class="container">
<h1>Anonymous scroll timeline</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Commodo viverra maecenas
accumsan lacus. Orci sagittis eu volutpat odio facilisis mauris. Eu nisl
nunc mi ipsum faucibus vitae aliquet nec. Amet nisl purus in mollis nunc
sed. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam.
Lorem sed risus ultricies tristique nulla. Commodo sed egestas egestas
fringilla phasellus faucibus. Semper eget duis at tellus at urna condimentum
mattis pellentesque. Porta lorem mollis aliquam ut porttitor leo a diam. At
lectus urna duis convallis convallis tellus id interdum velit. Placerat orci
nulla pellentesque dignissim enim sit amet venenatis urna. Rutrum tellus
pellentesque eu tincidunt tortor. Nulla facilisi cras fermentum odio eu
feugiat. Aliquet risus feugiat in ante metus. Quis imperdiet massa tincidunt
nunc pulvinar sapien et. Vel pharetra vel turpis nunc.
</p>
<p>
Potenti nullam ac tortor vitae purus. Tempor orci dapibus ultrices in
iaculis nunc sed augue. Adipiscing elit duis tristique sollicitudin nibh.
Luctus accumsan tortor posuere ac ut consequat semper. Enim nulla aliquet
porttitor lacus. Netus et malesuada fames ac. Aliquam ultrices sagittis orci
a scelerisque. Fringilla phasellus faucibus scelerisque eleifend donec
pretium vulputate sapien. Nibh praesent tristique magna sit amet purus
gravida quis. Mi proin sed libero enim sed faucibus turpis in eu. Natoque
penatibus et magnis dis parturient montes nascetur ridiculus. Pellentesque
elit ullamcorper dignissim cras tincidunt lobortis. Nunc faucibus a
pellentesque sit amet porttitor eget dolor. Luctus accumsan tortor posuere
ac ut. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper
velit. Ac odio tempor orci dapibus ultrices in iaculis nunc sed.
</p>
<p>
Molestie ac feugiat sed lectus vestibulum mattis. Elementum curabitur vitae
nunc sed velit dignissim sodales ut. Netus et malesuada fames ac turpis
egestas sed tempus. Viverra nam libero justo laoreet sit amet cursus sit
amet. Maecenas sed enim ut sem viverra aliquet eget. Et netus et malesuada
fames ac turpis egestas maecenas pharetra. Imperdiet proin fermentum leo vel
orci porta. Nunc eget lorem dolor sed viverra ipsum nunc aliquet. Facilisis
mauris sit amet massa vitae. Cras semper auctor neque vitae. Adipiscing diam
donec adipiscing tristique risus. Scelerisque eu ultrices vitae auctor eu.
Adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus urna. Egestas
quis ipsum suspendisse ultrices gravida. Semper quis lectus nulla at
volutpat diam. Egestas congue quisque egestas diam in arcu.
</p>
<p>
Est velit egestas dui id ornare arcu odio ut sem. Tortor consequat id porta
nibh venenatis. Proin sagittis nisl rhoncus mattis rhoncus urna neque. Porta
non pulvinar neque laoreet suspendisse interdum. Lacus vel facilisis
volutpat est velit egestas dui. Facilisi morbi tempus iaculis urna id
volutpat. Venenatis urna cursus eget nunc scelerisque viverra. Ultrices
gravida dictum fusce ut. Eu augue ut lectus arcu. Orci dapibus ultrices in
iaculis. Rhoncus mattis rhoncus urna neque viverra justo nec ultrices. Odio
eu feugiat pretium nibh ipsum consequat. Accumsan in nisl nisi scelerisque
eu ultrices vitae. Nunc faucibus a pellentesque sit. Ultricies integer quis
auctor elit sed vulputate mi. Nulla aliquet enim tortor at auctor urna nunc
id cursus.
</p>
<p>
Integer enim neque volutpat ac tincidunt vitae semper. Condimentum lacinia
quis vel eros donec ac odio tempor orci. Imperdiet dui accumsan sit amet
nulla facilisi morbi tempus. Suspendisse potenti nullam ac tortor vitae. Non
sodales neque sodales ut. Elementum eu facilisis sed odio. Aliquet nec
ullamcorper sit amet risus nullam eget felis eget. Diam phasellus vestibulum
lorem sed risus ultricies tristique. Facilisis sed odio morbi quis. Diam
quis enim lobortis scelerisque fermentum dui faucibus. Ullamcorper dignissim
cras tincidunt lobortis feugiat vivamus at augue eget. Platea dictumst
vestibulum rhoncus est pellentesque elit ullamcorper dignissim.
</p>
</div>
* {
box-sizing: border-box;
}
body {
font-family: "Helvetica", sans-serif;
line-height: 1.6;
min-height: 300vh;
margin: 0;
font-size: clamp(1rem, 1rem + 1vw, 1.5rem);
}
h1 {
line-height: 1.25;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: clamp(1rem, 2vw, 5rem);
}
.progress {
height: 1rem;
background: blue;
position: fixed;
top: 0;
left: 0;
width: 100%;
transform-origin: 0 50%;
animation:
scaleProgress auto linear forwards,
colorChange auto linear forwards;
animation-timeline: scroll(root);
}
@keyframes scaleProgress {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
@keyframes colorChange {
0% {
background-color: red;
}
50% {
background-color: yellow;
}
100% {
background-color: lime;
}
}
虽然我们在前面的例子中特意选择了线性缓动,但我们也可以使用 steps() 缓动来实现一些有趣的效果。这个例子展示了一种不同类型的进度条,它采用离散步长而不是平滑的线性缩放。我们为进度条元素设置了线性渐变背景作为颜色段,然后通过动画化 clip-path 来逐个显示它们。
.progress {
background: linear-gradient(
to right,
red 20%,
orange 0,
orange 40%,
yellow 0,
yellow 60%,
lime 0,
lime 80%,
green 0
);
animation: clip auto steps(5) forwards;
animation-timeline: scroll(root);
}
@keyframes clip {
0% {
clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
}
100% {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
}
* {
box-sizing: border-box;
}
body {
font-family: "Helvetica", sans-serif;
line-height: 1.6;
min-height: 300vh;
margin: 0;
font-size: clamp(1rem, 1rem + 1vw, 1.5rem);
}
h1 {
line-height: 1.25;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: clamp(1rem, 2vw, 5rem);
}
.progress {
height: 1rem;
background: linear-gradient(
to right,
red 20%,
orange 0,
orange 40%,
yellow 0,
yellow 60%,
lime 0,
lime 80%,
green 0
);
position: fixed;
top: 0;
left: 0;
width: 100%;
transform-origin: 0 50%;
animation: clip auto steps(5) forwards;
animation-timeline: scroll(root);
}
@keyframes clip {
0% {
clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
}
100% {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
}
<div class="progress"></div>
<div class="container">
<h1>Anonymous scroll timeline</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Commodo viverra maecenas
accumsan lacus. Orci sagittis eu volutpat odio facilisis mauris. Eu nisl
nunc mi ipsum faucibus vitae aliquet nec. Amet nisl purus in mollis nunc
sed. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam.
Lorem sed risus ultricies tristique nulla. Commodo sed egestas egestas
fringilla phasellus faucibus. Semper eget duis at tellus at urna condimentum
mattis pellentesque. Porta lorem mollis aliquam ut porttitor leo a diam. At
lectus urna duis convallis convallis tellus id interdum velit. Placerat orci
nulla pellentesque dignissim enim sit amet venenatis urna. Rutrum tellus
pellentesque eu tincidunt tortor. Nulla facilisi cras fermentum odio eu
feugiat. Aliquet risus feugiat in ante metus. Quis imperdiet massa tincidunt
nunc pulvinar sapien et. Vel pharetra vel turpis nunc.
</p>
<p>
Potenti nullam ac tortor vitae purus. Tempor orci dapibus ultrices in
iaculis nunc sed augue. Adipiscing elit duis tristique sollicitudin nibh.
Luctus accumsan tortor posuere ac ut consequat semper. Enim nulla aliquet
porttitor lacus. Netus et malesuada fames ac. Aliquam ultrices sagittis orci
a scelerisque. Fringilla phasellus faucibus scelerisque eleifend donec
pretium vulputate sapien. Nibh praesent tristique magna sit amet purus
gravida quis. Mi proin sed libero enim sed faucibus turpis in eu. Natoque
penatibus et magnis dis parturient montes nascetur ridiculus. Pellentesque
elit ullamcorper dignissim cras tincidunt lobortis. Nunc faucibus a
pellentesque sit amet porttitor eget dolor. Luctus accumsan tortor posuere
ac ut. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper
velit. Ac odio tempor orci dapibus ultrices in iaculis nunc sed.
</p>
<p>
Molestie ac feugiat sed lectus vestibulum mattis. Elementum curabitur vitae
nunc sed velit dignissim sodales ut. Netus et malesuada fames ac turpis
egestas sed tempus. Viverra nam libero justo laoreet sit amet cursus sit
amet. Maecenas sed enim ut sem viverra aliquet eget. Et netus et malesuada
fames ac turpis egestas maecenas pharetra. Imperdiet proin fermentum leo vel
orci porta. Nunc eget lorem dolor sed viverra ipsum nunc aliquet. Facilisis
mauris sit amet massa vitae. Cras semper auctor neque vitae. Adipiscing diam
donec adipiscing tristique risus. Scelerisque eu ultrices vitae auctor eu.
Adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus urna. Egestas
quis ipsum suspendisse ultrices gravida. Semper quis lectus nulla at
volutpat diam. Egestas congue quisque egestas diam in arcu.
</p>
<p>
Est velit egestas dui id ornare arcu odio ut sem. Tortor consequat id porta
nibh venenatis. Proin sagittis nisl rhoncus mattis rhoncus urna neque. Porta
non pulvinar neque laoreet suspendisse interdum. Lacus vel facilisis
volutpat est velit egestas dui. Facilisi morbi tempus iaculis urna id
volutpat. Venenatis urna cursus eget nunc scelerisque viverra. Ultrices
gravida dictum fusce ut. Eu augue ut lectus arcu. Orci dapibus ultrices in
iaculis. Rhoncus mattis rhoncus urna neque viverra justo nec ultrices. Odio
eu feugiat pretium nibh ipsum consequat. Accumsan in nisl nisi scelerisque
eu ultrices vitae. Nunc faucibus a pellentesque sit. Ultricies integer quis
auctor elit sed vulputate mi. Nulla aliquet enim tortor at auctor urna nunc
id cursus.
</p>
<p>
Integer enim neque volutpat ac tincidunt vitae semper. Condimentum lacinia
quis vel eros donec ac odio tempor orci. Imperdiet dui accumsan sit amet
nulla facilisi morbi tempus. Suspendisse potenti nullam ac tortor vitae. Non
sodales neque sodales ut. Elementum eu facilisis sed odio. Aliquet nec
ullamcorper sit amet risus nullam eget felis eget. Diam phasellus vestibulum
lorem sed risus ultricies tristique. Facilisis sed odio morbi quis. Diam
quis enim lobortis scelerisque fermentum dui faucibus. Ullamcorper dignissim
cras tincidunt lobortis feugiat vivamus at augue eget. Platea dictumst
vestibulum rhoncus est pellentesque elit ullamcorper dignissim.
</p>
</div>
滚动进度动画可以与现有的 animation-direction 和 animation-iteration-count 属性结合使用。因此,我们可以让我们的动画在滚动时间线中重复多次,或者反向播放。这里,“球”在我们滚动时会弹跳几次。
.progress {
animation: bounce auto linear 6 alternate;
animation-timeline: scroll(root);
}
@keyframes bounce {
100% {
transform: translateY(-50vh);
}
}
<div class="progress"></div>
<div class="container">
<h1>Anonymous scroll timeline</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Commodo viverra maecenas
accumsan lacus. Orci sagittis eu volutpat odio facilisis mauris. Eu nisl
nunc mi ipsum faucibus vitae aliquet nec. Amet nisl purus in mollis nunc
sed. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam.
Lorem sed risus ultricies tristique nulla. Commodo sed egestas egestas
fringilla phasellus faucibus. Semper eget duis at tellus at urna condimentum
mattis pellentesque. Porta lorem mollis aliquam ut porttitor leo a diam. At
lectus urna duis convallis convallis tellus id interdum velit. Placerat orci
nulla pellentesque dignissim enim sit amet venenatis urna. Rutrum tellus
pellentesque eu tincidunt tortor. Nulla facilisi cras fermentum odio eu
feugiat. Aliquet risus feugiat in ante metus. Quis imperdiet massa tincidunt
nunc pulvinar sapien et. Vel pharetra vel turpis nunc.
</p>
<p>
Potenti nullam ac tortor vitae purus. Tempor orci dapibus ultrices in
iaculis nunc sed augue. Adipiscing elit duis tristique sollicitudin nibh.
Luctus accumsan tortor posuere ac ut consequat semper. Enim nulla aliquet
porttitor lacus. Netus et malesuada fames ac. Aliquam ultrices sagittis orci
a scelerisque. Fringilla phasellus faucibus scelerisque eleifend donec
pretium vulputate sapien. Nibh praesent tristique magna sit amet purus
gravida quis. Mi proin sed libero enim sed faucibus turpis in eu. Natoque
penatibus et magnis dis parturient montes nascetur ridiculus. Pellentesque
elit ullamcorper dignissim cras tincidunt lobortis. Nunc faucibus a
pellentesque sit amet porttitor eget dolor. Luctus accumsan tortor posuere
ac ut. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper
velit. Ac odio tempor orci dapibus ultrices in iaculis nunc sed.
</p>
<p>
Molestie ac feugiat sed lectus vestibulum mattis. Elementum curabitur vitae
nunc sed velit dignissim sodales ut. Netus et malesuada fames ac turpis
egestas sed tempus. Viverra nam libero justo laoreet sit amet cursus sit
amet. Maecenas sed enim ut sem viverra aliquet eget. Et netus et malesuada
fames ac turpis egestas maecenas pharetra. Imperdiet proin fermentum leo vel
orci porta. Nunc eget lorem dolor sed viverra ipsum nunc aliquet. Facilisis
mauris sit amet massa vitae. Cras semper auctor neque vitae. Adipiscing diam
donec adipiscing tristique risus. Scelerisque eu ultrices vitae auctor eu.
Adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus urna. Egestas
quis ipsum suspendisse ultrices gravida. Semper quis lectus nulla at
volutpat diam. Egestas congue quisque egestas diam in arcu.
</p>
<p>
Est velit egestas dui id ornare arcu odio ut sem. Tortor consequat id porta
nibh venenatis. Proin sagittis nisl rhoncus mattis rhoncus urna neque. Porta
non pulvinar neque laoreet suspendisse interdum. Lacus vel facilisis
volutpat est velit egestas dui. Facilisi morbi tempus iaculis urna id
volutpat. Venenatis urna cursus eget nunc scelerisque viverra. Ultrices
gravida dictum fusce ut. Eu augue ut lectus arcu. Orci dapibus ultrices in
iaculis. Rhoncus mattis rhoncus urna neque viverra justo nec ultrices. Odio
eu feugiat pretium nibh ipsum consequat. Accumsan in nisl nisi scelerisque
eu ultrices vitae. Nunc faucibus a pellentesque sit. Ultricies integer quis
auctor elit sed vulputate mi. Nulla aliquet enim tortor at auctor urna nunc
id cursus.
</p>
<p>
Integer enim neque volutpat ac tincidunt vitae semper. Condimentum lacinia
quis vel eros donec ac odio tempor orci. Imperdiet dui accumsan sit amet
nulla facilisi morbi tempus. Suspendisse potenti nullam ac tortor vitae. Non
sodales neque sodales ut. Elementum eu facilisis sed odio. Aliquet nec
ullamcorper sit amet risus nullam eget felis eget. Diam phasellus vestibulum
lorem sed risus ultricies tristique. Facilisis sed odio morbi quis. Diam
quis enim lobortis scelerisque fermentum dui faucibus. Ullamcorper dignissim
cras tincidunt lobortis feugiat vivamus at augue eget. Platea dictumst
vestibulum rhoncus est pellentesque elit ullamcorper dignissim.
</p>
</div>
* {
box-sizing: border-box;
}
body {
font-family: "Helvetica", sans-serif;
line-height: 1.6;
min-height: 300vh;
margin: 0;
font-size: clamp(1rem, 1rem + 1vw, 1.5rem);
}
h1 {
line-height: 1.25;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 0 clamp(1rem, 2vw, 5rem) 0 6rem;
}
.progress {
width: 3rem;
height: 3rem;
position: fixed;
top: calc(100vh - 4rem);
left: 1rem;
background: blue;
border-radius: 50%;
animation: bounce auto linear 6 alternate;
animation-timeline: scroll(root);
}
@keyframes bounce {
100% {
transform: translateY(-50vh);
}
}
有时,我们可能希望为不是滚动容器后代的元素设置动画,但仍将其动画与滚动容器的进度关联起来。为了做到这一点,我们需要创建一个命名滚动进度时间线。我们将通过使用简写属性 scroll-timeline(scroll-timeline-name 和 scroll-timeline-axis 的简写)在滚动容器上声明时间线的名称和轴。同样,块轴是默认值。时间线名称必须带有两个破折号前缀(类似于自定义属性),这可以确保它不会与其他属性值冲突。
滚动容器必须是一个具有滚动能力的元素。
.scroller {
max-height: 300px;
overflow: scroll;
scroll-timeline: --scale-progress block;
}
我们可以使用 animation-timeline 属性将我们要设置动画的元素与滚动时间线关联起来。
/* Sibling of .scroller */
.progress {
animation: scaleProgress auto linear;
animation-timeline: --scale-progress;
}
@keyframes scaleProgress {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
* {
box-sizing: border-box;
}
body {
line-height: 1.5;
}
.wrapper {
min-height: 100vh;
display: grid;
gap: 1rem;
grid-template-columns: auto auto;
justify-content: center;
align-content: center;
timeline-scope: --scale-progress;
animation: colorChange auto linear;
}
.scroller {
position: relative;
max-width: 300px;
max-height: 300px;
overflow: scroll;
border: 1px solid;
padding: 1rem;
scroll-timeline: --scale-progress;
}
.progress {
width: 5rem;
background: deeppink;
transform-origin: center 100%;
animation: scaleProgress auto linear forwards;
animation-timeline: --scale-progress;
}
@keyframes colorChange {
0% {
background-color: deeppink;
}
100% {
background-color: turquoise;
}
}
@keyframes scaleProgress {
0% {
scale: 1 0;
}
100% {
scale: 1 1;
}
}
<div class="wrapper">
<div class="scroller">
<h1>Scroll this</h1>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Hic vitae
voluptatem, in ipsa, magnam explicabo vero modi fuga recusandae voluptate
reprehenderit neque sequi labore delectus odio consequuntur illo cum
minus.
</p>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Hic vitae
voluptatem, in ipsa, magnam explicabo vero modi fuga recusandae voluptate
reprehenderit neque sequi labore delectus odio consequuntur illo cum
minus.
</p>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Hic vitae
voluptatem, in ipsa, magnam explicabo vero modi fuga recusandae voluptate
reprehenderit neque sequi labore delectus odio consequuntur illo cum
minus.
</p>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Reprehenderit
porro at officiis, voluptas delectus ratione vitae natus cum excepturi
eaque. Culpa ut cupiditate aspernatur expedita minima suscipit et quis
illo.
</p>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Reprehenderit
porro at officiis, voluptas delectus ratione vitae natus cum excepturi
eaque. Culpa ut cupiditate aspernatur expedita minima suscipit et quis
illo.
</p>
</div>
<div class="progress"></div>
</div>
只要我们要设置动画的元素是滚动容器的同级元素,这就可以正常工作。如果我们想为祖先元素或同级元素的后代元素设置动画怎么办?
我们还需要另一个 CSS 属性,timeline-scope,它允许我们修改命名时间线的范围,以包含设置该属性的元素。例如,如果我们将其设置在 body 上,我们就可以为该元素的背景颜色设置动画,尽管它是一个滚动容器的祖先。

让我们看一下代码。
/* Ancestor element: We want to scope the scroll timeline to include this element and its descendants */
body {
timeline-scope: --scale-progress;
/* Apply the animation */
animation: colorChange auto linear forwards;
animation-timeline: --scale-progress;
}
/* The scroll container on which we declare our timeline */
.scroller {
max-height: 300px;
overflow: scroll;
scroll-timeline: --scale-progress block;
}
/* Apply the animation on the sibling as before */
.progress {
animation: scaleProgress auto linear;
animation-timeline: --scale-progress;
}
注意: timeline-scope 目前仅在 Chrome Canary 和 Chrome 116 中支持,且需要启用实验性 Web 平台功能。
* {
box-sizing: border-box;
}
body {
line-height: 1.5;
min-height: 100vh;
display: grid;
gap: 1rem;
grid-template-columns: auto auto;
justify-content: center;
align-content: center;
timeline-scope: --scale-progress;
animation: colorChange auto linear forwards;
animation-timeline: --scale-progress;
}
.scroller {
position: relative;
max-width: 300px;
max-height: 300px;
overflow: scroll;
border: 1px solid;
padding: 1rem;
background: white;
scroll-timeline: --scale-progress;
}
.progress-wrapper {
border: 2px solid black;
padding: 0.5rem;
}
.progress {
width: 5rem;
height: 100%;
background: black;
transform-origin: center 100%;
animation: scaleProgress auto linear forwards;
animation-timeline: --scale-progress;
}
@keyframes colorChange {
0% {
background-color: deeppink;
}
100% {
background-color: turquoise;
}
}
@keyframes scaleProgress {
0% {
scale: 1 0;
}
100% {
scale: 1 1;
}
}
<div class="scroller">
<h1>Scroll this</h1>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Hic vitae
voluptatem, in ipsa, magnam explicabo vero modi fuga recusandae voluptate
reprehenderit neque sequi labore delectus odio consequuntur illo cum minus.
</p>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Hic vitae
voluptatem, in ipsa, magnam explicabo vero modi fuga recusandae voluptate
reprehenderit neque sequi labore delectus odio consequuntur illo cum minus.
</p>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Hic vitae
voluptatem, in ipsa, magnam explicabo vero modi fuga recusandae voluptate
reprehenderit neque sequi labore delectus odio consequuntur illo cum minus.
</p>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Reprehenderit
porro at officiis, voluptas delectus ratione vitae natus cum excepturi
eaque. Culpa ut cupiditate aspernatur expedita minima suscipit et quis illo.
</p>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Reprehenderit
porro at officiis, voluptas delectus ratione vitae natus cum excepturi
eaque. Culpa ut cupiditate aspernatur expedita minima suscipit et quis illo.
</p>
</div>
<div class="progress-wrapper">
<div class="progress"></div>
</div>
到目前为止,我们已经创建了一些相当基本的进度条动画——这可能是滚动进度时间线最明显的用例之一。但是,没有任何东西能阻止我们用滚动动画发挥创意。
在用户垂直滚动时水平动画化元素可以使网页感觉更具动态性,不那么线性。这里,我们动画化一系列图片,让它们在用户垂直滚动时从左侧滑入。
@layer reset;
body {
font-family: "Helvetica", sans-serif;
min-height: 300vh;
color: white;
}
.wrapper {
display: flex;
position: fixed;
top: 0;
left: 0;
height: 100vh;
animation: slide auto linear;
animation-timeline: scroll();
}
@keyframes slide {
0% {
translate: 0;
}
100% {
translate: calc(-100% - 100vw);
}
}
section {
min-height: 100vh;
position: relative;
z-index: 1;
}
.container {
background: rgba(46, 42, 181, 0.45);
min-height: 100vh;
padding: 1em clamp(1rem, 2vw, 4vw);
backdrop-filter: grayscale(100%);
}
figure {
flex: 0 0 100vw;
}
img {
height: 100%;
object-fit: cover;
}
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-size: 1.2rem;
}
@media (min-width: 1200px) {
html {
font-size: 20px;
}
}
img {
display: block;
width: 100%;
}
figure {
margin: 0;
position: relative;
}
}
<div class="wrapper">
<figure>
<img
src="https://images.unsplash.com/photo-1550098612-4838745601bf?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2ODcyNzAxMjV8&ixlib=rb-4.0.3&q=85"
alt="" />
</figure>
<figure>
<img
src="https://images.unsplash.com/photo-1607240367835-bdbf309c1e06?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2ODcyNzAxMjV8&ixlib=rb-4.0.3&q=85"
alt="" />
</figure>
<figure>
<img
src="https://images.unsplash.com/photo-1616010107983-b006a14939f2?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2ODcyNzAxMjV8&ixlib=rb-4.0.3&q=85"
alt="" />
</figure>
</div>
<section>
<div class="container">
<h1>Section 1</h1>
<p>Scroll vertically and the images move horizontally</p>
</div>
</section>
<section>
<div class="container">
<h2>Section 2</h2>
</div>
</section>
<section>
<div class="container">
<h2>Section 3</h2>
</div>
</section>
在 CodePen 上查看完整示例。
我们可以使用 offset-path 在 CSS 中定位和动画化元素沿路径移动,以定义元素要遵循的运动路径。这比矩形进度条有趣得多!
* {
box-sizing: border-box;
}
body {
width: 100%;
min-height: 300vh;
margin: 0;
background: linear-gradient(to bottom, #2d2a82, lightblue);
background-size: 100% 300vh;
}
.progress {
position: fixed;
top: 3rem;
left: 3rem;
width: 3rem;
height: auto;
fill: currentColor;
z-index: 1;
offset-path: path(
"M.5 122.7s24.7-275 276.9 0c327.1 356.7 266.1-330.3 548-33.3 256.9 270.7 271.1 0 271.1 0"
);
animation: move auto linear;
animation-timeline: scroll(root);
}
.cloud {
width: 12vw;
height: auto;
position: absolute;
top: 5vh;
left: 60vw;
fill: rgb(255 255 255 / 0.5);
}
.cloud:nth-child(2n) {
top: 100vh;
left: 15vw;
}
.cloud:nth-child(3n) {
top: 160vh;
left: 70vw;
}
@keyframes move {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
<svg viewBox="0 0 640 512" width="100" title="fighter-jet" class="progress">
<path
d="M544 224l-128-16-48-16h-24L227.158 44h39.509C278.333 44 288 41.375 288 38s-9.667-6-21.333-6H152v12h16v164h-48l-66.667-80H18.667L8 138.667V208h8v16h48v2.666l-64 8v42.667l64 8V288H16v16H8v69.333L18.667 384h34.667L120 304h48v164h-16v12h114.667c11.667 0 21.333-2.625 21.333-6s-9.667-6-21.333-6h-39.509L344 320h24l48-16 128-16c96-21.333 96-26.583 96-32 0-5.417 0-10.667-96-32z" />
</svg>
<svg viewBox="0 0 640 512" width="0" height="0" title="cloud">
<defs>
<path
id="cloud"
d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4z" />
</defs>
</svg>
<svg viewBox="0 0 640 512" width="100" title="cloud" class="cloud">
<use href="#cloud"></use>
</svg>
<svg viewBox="0 0 640 512" width="100" title="cloud" class="cloud">
<use href="#cloud"></use>
</svg>
<svg viewBox="0 0 640 512" width="100" title="cloud" class="cloud">
<use href="#cloud"></use>
</svg>
在 CodePen 上查看完整示例。
在此演示中,我们在滚动时为多个元素设置动画:文本被显示出来,而盒子从左到右滑动并翻转。为了简化代码并避免创建多个关键帧,我们动画化了一个自定义属性,并使用三角函数来计算 translateY 值,这些函数在所有主要浏览器的最新版本中都受支持。与 transform 属性不同,自定义属性是在主线程上进行动画处理的,这意味着如果您试图为它们中的许多设置动画,您的网站可能会出现性能不佳的情况。
* {
box-sizing: border-box;
}
body {
--offset: max(1rem, 3vw);
--boxSize: clamp(2.5rem, 10vw, 200px);
--boxSize: 15vw;
margin: 0;
padding: var(--offset);
min-height: 300vh;
background-color: pink;
font-family: "Helvetica", sans-serif;
animation: colorChange auto linear;
animation-timeline: scroll(root block);
}
.wrapper {
min-height: 100vh;
position: fixed;
top: 0;
left: 0;
padding: var(--offset);
display: flex;
flex-wrap: wrap;
align-items: center;
align-content: center;
}
h1 {
flex: 1 0 100%;
animation: clip auto linear;
animation-timeline: scroll(root block);
font-size: clamp(2rem, 4vw + 1rem, 6rem);
}
@property --i {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
.box {
--i: 1;
--angle: calc((var(--i) - 1) * (360deg / 5));
--amplitude: 9vw;
--x: calc(var(--i) * var(--boxSize));
width: var(--boxSize);
aspect-ratio: 1;
background:
radial-gradient(circle at 25% 50%, black 10%, transparent 0),
radial-gradient(circle at 75% 50%, black 10%, transparent 0),
radial-gradient(circle at 50% 0, black 10%, transparent 0) deeppink;
background-position:
center center,
center center,
center calc(var(--boxSize) * 0.6),
center center;
background-size:
100% 100%,
100% 100%,
100% 100%;
background-repeat: no-repeat;
border-radius: max(10%, 0.2rem);
border: min(5px, 1vw) solid;
animation:
move auto linear,
spin auto linear;
animation-timeline: scroll(root block);
translate: calc(var(--x) - var(--boxSize))
calc(sin(var(--angle)) * var(--amplitude));
}
@keyframes move {
0% {
--i: 1;
}
100% {
--i: 6;
}
}
@keyframes spin {
100% {
rotate: 360deg;
}
}
@keyframes colorChange {
100% {
background-color: turquoise;
}
}
@keyframes clip {
0% {
clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
}
100% {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
}
<div class="wrapper">
<h1>We love to scroll!</h1>
<div class="box"></div>
</div>
在 CodePen 上查看完整示例。
与任何侵入性动画一样,我们应该始终优先考虑可访问性,并确保为那些宁愿不使用动画的用户关闭动画。这对于滚动驱动动画尤其重要,因为即使是那些通常不患有前庭疾病的用户,也可能引起晕动症。如果您想了解更多信息,请参阅尊重用户的运动偏好,了解如何使用 prefers-reduced-motion 媒体查询来确保您的动画是可访问的。
那么,CSS 中的滚动时间线动画与 JavaScript 库(一旦它们得到普遍支持)相比如何?如果您正在创建特别复杂的动画,您可能仍然需要使用像 GSAP 这样的库,它特别擅长处理复杂的编排。库也可能为我们提供自定义缓动等功能,以及 GSAP 的 Inertia 插件(允许动画在滚动停止后滑动停止,而不是 abrupt 停止)。目前,我们还没有在 CSS 中检测元素当前是否正在滚动的办法。
同样,如果您的动画对用户体验至关重要,您可能需要暂时等待,因为滚动链接动画普遍得到支持还需要一段时间。
另一方面,如果您需要一些相对简单的滚动驱动动画,CSS 可以为您(和您的用户)节省大量 JavaScript 负载,从而在性能上获得巨大提升!
希望您喜欢阅读本文并探索示例。如果您有任何反馈、想法或问题,请随时在 Discord 或 GitHub 上留言。