滾動連結動畫通常能為網站增添一抹亮色,但長期以來一直被 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 上留言。