瞭解如何使用捲動時間軸和檢視畫面時間軸,以宣告式方式建立捲動導向的動畫。
以捲動為主的動畫
捲動式動畫是網路上常見的使用者體驗模式。捲動導向的動畫會連結至捲動容器的捲動位置。這表示當你向上或向下捲動時,連結的動畫會在直接回應中向前或向後捲動。例如視差背景圖片或閱讀指標時,系統會隨著捲動畫面移動移動。
捲動驅動的動畫類型也類似,可連結至元素在捲動容器中的位置。例如,元素在進入畫面時會淡入淡出。
如要達成這些類型的效果,最簡單的方式就是回應主執行緒的捲動事件,而這會導致兩個主要問題:
- 新式瀏覽器在獨立程序上執行捲動作業,因此以非同步方式傳送捲動事件。
- 主執行緒動畫會受卡頓影響。
這使得製作難以或難以與捲動同步的高效捲動式動畫。
自 Chrome 115 版起,我們提供一組新的 API 和概念,可用於啟用宣告式捲動驅動動畫:捲動時間軸和檢視畫面時間軸。
這些新概念與現有的 Web Animations API (WAAPI) 和 CSS Animations API 整合,讓其沿用現有 API 帶來的優勢。包括在主執行緒外執行捲動導向動畫。沒錯,只要加入幾行額外程式碼,就能以捲動方式驅動的流暢動畫,並透過捲動方式在主執行緒中執行。不喜歡什麼?!
在網路上製作動畫,簡單複習
透過 CSS 在網頁上使用動畫
如要在 CSS 建立動畫,請使用 @keyframes
規則定義一組主要畫面格。使用 animation-name
屬性與元素建立連結,同時設定 animation-duration
來決定動畫所需時間。還有更多 animation-*
可用的長篇屬性,animation-easing-function
和 animation-fill-mode
只是為了列舉幾種屬性,也可在 animation
簡寫中加以結合。
舉例來說,以下動畫在 X 軸上放大元素,同時變更背景顏色:
@keyframes scale-up {
from {
background-color: red;
transform: scaleX(0);
}
to {
background-color: darkred;
transform: scaleX(1);
}
}
#progressbar {
animation: 2.5s linear forwards scale-up;
}
透過 JavaScript 在網頁上使用動畫
在 JavaScript 中,Web Animations API 可以達到完全相同。方法是建立新的 Animation
和 KeyFrameEffect
執行個體,或使用較短的 Element
animate()
方法。
document.querySelector('#progressbar').animate(
{
backgroundColor: ['red', 'darkred'],
transform: ['scaleX(0)', 'scaleX(1)'],
},
{
duration: 2500,
fill: 'forwards',
easing: 'linear',
}
);
上述 JavaScript 程式碼片段的視覺結果與先前的 CSS 版本相同。
動畫時間軸
根據預設,附加至元素的動畫會在文件時間軸上執行。來源時間是從網頁載入的 0 開始,且隨時鐘時間的進度開始持續不斷。這是預設動畫時間軸,截至目前為止,您只能使用這種時間軸。
捲動驅動動畫規格定義了兩種可使用的新時間軸類型:
- 捲動進度時間軸:連結至特定軸的捲動容器捲動位置的時間軸。
- 查看進度時間軸:連結至捲動容器中特定元素的相對位置的時間軸。
捲動進度時間軸
捲動進度時間軸是動畫時間軸,連結至捲動容器內捲動位置的進度 (也稱為「捲軸」或「捲動式」) 與特定軸長。這項功能會將捲動範圍內的位置轉換為進度百分比。
起始捲動位置代表進度 0%,結束捲動位置則代表 100% 進度。在下方圖表中,只要您從上往下捲動捲動器,就會發現進度從 0% 提高到 100%。
✨ 親自體驗
捲動進度時間軸通常縮寫為「捲動時間軸」。
查看進度時間軸
這類時間軸會連結至捲動容器中特定元素的相對進度。如同捲動進度時間軸,系統也會追蹤捲軸的捲動偏移。與捲動進度時間軸不同,後者是捲軸內主題的相對位置,可以決定進度。
這與 IntersectionObserver
的運作方式類似,可以追蹤元素在捲動器中顯示的程度。如果捲動器看不到該元素,則該元素不會相交。如果出現在捲動器中,即使是最小部分也一樣,就會相交。
檢視畫面進度時間軸會從主題開始與捲動器互動的那一刻開始,並在主題停止與捲動器互動時結束。在接下來的圖表中,您可以看到當主體進入捲動容器時,進度從 0% 開始增加,並在主體離開捲動容器時達到 100%。
✨ 親自體驗
「觀看進度時間軸」通常簡稱為「查看時間軸」。您可以根據主題規模,指定「檢視畫面時間軸」的特定部分,但日後會再採取其他做法。
瞭解捲動進度時間軸
在 CSS 中建立匿名捲動進度時間軸
如要在 CSS 中建立捲動時間軸,最簡單的方法是使用 scroll()
函式。這會建立匿名的捲動時間軸,您可以將該時間軸設為新 animation-timeline
屬性的值。
範例:
@keyframes animate-it { … }
.subject {
animation: animate-it linear;
animation-timeline: scroll(root block);
}
scroll()
函式接受 <scroller>
和 <axis>
引數。
可接受的 <scroller>
引數值如下:
nearest
:使用最近的祖系捲動容器 (預設)。root
:使用文件可視區域做為捲動容器。self
:使用元素本身做為捲動容器。
可接受的 <axis>
引數值如下:
block
:使用捲動容器區塊軸的進度測量指標 (預設)。inline
:使用捲動容器內嵌軸的進度測量指標。y
:使用捲動容器 Y 軸的進度測量指標。x
:使用捲動容器的 X 軸測量進度。
舉例來說,如要將動畫繫結至區塊軸的根捲動器,傳遞至 scroll()
的值為 root
和 block
。兩邊的值是 scroll(root block)
。
示範:閱讀進度指標
這項示範的閱讀進度指標固定在可視區域頂端。當你向下捲動頁面時,進度列會持續成長,直到文件結尾處佔滿整個可視區域寬度。匿名捲動進度時間軸的作用在於呈現動畫。
✨ 親自體驗
閱讀進度指標是固定在頁面頂端的位置,如要使用合成動畫,請勿為 width
加上動畫效果,但元素使用 transform
在 x 軸上縮小。
<body>
<div id="progress"></div>
…
</body>
@keyframes grow-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
#progress {
position: fixed;
left: 0; top: 0;
width: 100%; height: 1em;
background: red;
transform-origin: 0 50%;
animation: grow-progress auto linear;
animation-timeline: scroll();
}
#progress
元素上動畫 grow-progress
的時間軸已設為使用 scroll()
建立的匿名時間軸。沒有給 scroll()
的引數,因此會改回使用預設值。
要追蹤的預設捲軸是 nearest
,預設軸為 block
。這會有效指定根捲動器,因為這會追蹤 #progress
元素的距離最近的捲軸,並追蹤其區塊方向。
在 CSS 中建立已命名的捲動進度時間軸
另一個定義捲動進度時間軸的方法是使用已命名的時間軸。雖然較為複雜,但當您未指定上層捲動器或根捲動器時,或者當網頁使用多個時間軸,或是自動查詢功能無法運作時,這項做法就很實用。如此一來,即可利用您指定的名稱識別捲動進度時間軸。
如要為元素建立已命名的捲動進度時間軸,請將捲動容器的 scroll-timeline-name
CSS 屬性設為偏好的 ID。值的開頭必須是 --
。
如要調整要追蹤的軸,請一併宣告 scroll-timeline-axis
屬性。允許的值與 scroll()
的 <axis>
引數相同。
最後,如要將動畫連結至捲動進度時間軸,請針對要採用動畫效果的元素設定 animation-timeline
屬性,並將其值設為與 scroll-timeline-name
所使用的 ID 相同的值。
程式碼範例:
@keyframes animate-it { … }
.scroller {
scroll-timeline-name: --my-scroller;
scroll-timeline-axis: inline;
}
.scroller .subject {
animation: animate-it linear;
animation-timeline: --my-scroller;
}
您可以視需要在 scroll-timeline
中結合 scroll-timeline-name
和 scroll-timeline-axis
。例如:
scroll-timeline: --my-scroller inline;
示範:水平輪轉步驟指標
此示範是指每張圖片輪轉介面上方的步驟指標。如果輪轉介麵包含三張圖片,指標列的寬度會是 33%,表示您目前看到的是三張圖片之一。當最後一張圖片在檢視畫面中 (取決於捲動者已捲動至末端) 時,指標會佔滿捲動器的完整寬度。已命名的捲動進度時間軸可用來呈現動畫。
✨ 親自體驗
圖片庫的基本標記如下:
<div class="gallery" style="--num-images: 2;">
<div class="gallery__scrollcontainer">
<div class="gallery__progress"></div>
<div class="gallery__entry">…</div>
<div class="gallery__entry">…</div>
</div>
</div>
.gallery__progress
元素會完全放置在 .gallery
包裝函式元素中。其初始大小取決於 --num-images
自訂屬性。
.gallery {
position: relative;
}
.gallery__progress {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 1em;
transform: scaleX(calc(1 / var(--num-images)));
}
.gallery__scrollcontainer
會橫向排列內含的 .gallery__entry
元素,而且是捲動的元素。透過追蹤其捲動位置,.gallery__progress
就能產生動畫。只要參照已命名的捲動進度時間軸 --gallery__scrollcontainer
即可。
@keyframes grow-progress {
to { transform: scaleX(1); }
}
.gallery__scrollcontainer {
overflow-x: scroll;
scroll-timeline: --gallery__scrollcontainer inline;
}
.gallery__progress {
animation: auto grow-progress linear forwards;
animation-timeline: --gallery__scrollcontainer;
}
使用 JavaScript 建立捲動進度時間軸
如要在 JavaScript 中建立捲動時間軸,請建立新的 ScrollTimeline
類別執行個體。使用您想追蹤的 source
和 axis
傳入房源包。
source
:要追蹤的捲軸元素的參照。使用document.documentElement
指定根捲動器。axis
:決定要追蹤的軸。與 CSS 子類類似,可接受的值為block
、inline
、x
和y
。
const tl = new ScrollTimeline({
source: document.documentElement,
});
如要將註解附加到網路動畫,請以 timeline
屬性的形式傳入,並省略任何 duration
(如果有的話)。
$el.animate({
opacity: [0, 1],
}, {
timeline: tl,
});
示範:朗讀進度指標、再次查看
如要以 JavaScript 重新建立閱讀進度指標,並使用相同的標記,請使用下列 JavaScript 程式碼:
const $progressbar = document.querySelector('#progress');
$progressbar.style.transformOrigin = '0% 50%';
$progressbar.animate(
{
transform: ['scaleX(0)', 'scaleX(1)'],
},
{
fill: 'forwards',
timeline: new ScrollTimeline({
source: document.documentElement,
}),
}
);
CSS 版本的視覺結果相同:建立的 timeline
會追蹤根捲動器,並在您捲動頁面時,將 X 軸上的 #progress
向上縮放 0% 到 100%。
✨ 親自體驗
運用查看進度時間軸
在 CSS 中建立匿名查看進度時間軸
如要建立觀看進度時間軸,請使用 view()
函式。系統接受的引數為 <axis>
和 <view-timeline-inset>
。
<axis>
與捲動進度時間軸相同,且會定義要追蹤的軸。預設值為block
。- 使用
<view-timeline-inset>
時,您可以指定位移 (正數或負數),在系統判定元素是否出現在檢視畫面中時,調整邊界。值必須是百分比或auto
,且auto
為預設值。
舉例來說,如要將動畫繫結至元素與區塊軸上捲動器重疊的元素,請使用 view(block)
。與 scroll()
類似,將此項目設為 animation-timeline
屬性的值,而且別忘了將 animation-duration
設為 auto
。
使用下列程式碼時,每個 img
會在捲動時越過可視區域,因此淡出。
@keyframes reveal {
from { opacity: 0; }
to { opacity: 1; }
}
img {
animation: reveal linear;
animation-timeline: view();
}
Intermezzo:查看時間軸範圍
根據預設,連結至檢視畫面時間軸的動畫會附加至整個時間軸範圍。這會從主體即將進入捲軸的那一刻開始,並在主體完全離開捲軸時結束。
你也可以指定其附加範圍,將時間軸連結至「檢視畫面時間軸」的特定部分。例如當主題進入捲動器時。在下方的圖表中,當主體進入捲動容器時,進度從 0% 開始計時,但在完全相交的那一刻,到目前已經達到 100%。
您可以指定的「查看時間軸」範圍如下:
cover
:代表檢視進度時間軸的完整範圍。entry
:代表主體方塊進入檢視進度瀏覽權限範圍的範圍。exit
:代表主體方塊離開檢視進度瀏覽權限範圍的範圍。entry-crossing
:代表主要方塊跨越終點框線的範圍。exit-crossing
:代表主體方塊跨越起始邊框邊緣的範圍。contain
:代表捲動通訊埠內,主體方塊完全包含或完全覆蓋的範圍。這取決於拍攝主體是否高度高於或低於捲動器。
如要定義範圍,您必須設定範圍開始和範圍結束。每個關鍵字的範圍名稱都是由範圍名稱 (請參閱上方清單) 和範圍偏移組成,用於決定該範圍名稱內的排名。範圍偏移值通常介於 0%
至 100%
之間,但您也可以指定固定長度,例如 20em
。
舉例來說,如果您想要從拍攝主體進入的那一刻開始執行動畫,請選擇 entry 0%
做為範圍的起始值。如要在主體輸入時結束文字,請選擇 entry 100%
做為範圍端點的值。
在 CSS 中,請使用 animation-range
屬性進行設定。範例:
animation-range: entry 0% entry 100%;
在 JavaScript 中,使用 rangeStart
和 rangeEnd
屬性。
$el.animate(
keyframes,
{
timeline: tl,
rangeStart: 'entry 0%',
rangeEnd: 'entry 100%',
}
);
請使用下方內嵌的工具,查看每個範圍名稱代表的意義,以及百分比對開始和結束位置的影響。請嘗試將範圍開始設為 entry 0%
,範圍結尾設為 cover 50%
,然後拖曳捲軸來查看動畫結果。
觀看錄影
您可能會注意到,在試用「檢視時間軸範圍」工具時,可以用兩種不同的「範圍名稱 + 範圍偏移」組合來指定某些範圍。舉例來說,entry 0%
、entry-crossing 0%
和 cover 0%
都指定同一個區域。
如果範圍開始和範圍端指定相同的範圍名稱,且涵蓋整個範圍 (從 0%,到 100%),您可以縮短該值,使其直接成為範圍名稱。例如,animation-range: entry 0% entry 100%;
可以重新編寫為較短的 animation-range: entry
。
示範:圖片顯示
此示範範例會在使用者進入捲軸時在圖像中淡出。請使用匿名檢視畫面時間軸。動畫範圍已經過調整,讓每張圖片在捲動器的一半時,都有完整的不透明度。
✨ 親自體驗
展開效果的方法是使用動畫的裁剪路徑。用於這個效果的 CSS 如下:
@keyframes reveal {
from { opacity: 0; clip-path: inset(0% 60% 0% 50%); }
to { opacity: 1; clip-path: inset(0% 0% 0% 0%); }
}
.revealing-image {
animation: auto linear reveal both;
animation-timeline: view();
animation-range: entry 25% cover 50%;
}
在 CSS 中建立已命名的「查看進度時間軸」
如同捲動時間軸為版本命名,您也可以建立名為「查看時間軸」的稱呼。請使用包含 view-timeline-
前置字元的變化版本 (即 view-timeline-name
和 view-timeline-axis
),而不是 scroll-timeline-*
屬性。
適用的值類型必須相同,系統也會適用查詢已命名時間軸的規則。
示範:圖片顯示、再審
重新建構圖片來示範稍早的示範,修改過的程式碼會如下所示:
.revealing-image {
view-timeline-name: --revealing-image;
view-timeline-axis: block;
animation: auto linear reveal both;
animation-timeline: --revealing-image;
animation-range: entry 25% cover 50%;
}
使用 view-timeline-name: revealing-image
時,系統會在其最接近的捲動器內追蹤元素。接著會使用相同的值做為 animation-timeline
屬性的值。輸出內容與先前完全相同。
✨ 親自體驗
在 JavaScript 中建立觀看進度時間軸
如要在 JavaScript 中建立檢視畫面時間軸,請為 ViewTimeline
類別建立新的例項。使用要追蹤的 subject
、axis
和 inset
傳入房源包。
subject
:要在其捲軸內追蹤的元素參照。axis
:要追蹤的軸。與 CSS 子類類似,可接受的值為block
、inline
、x
和y
。inset
:判斷捲軸的插邊 (陽性) 或輸出 (負) 調整,用於判斷方塊是否在檢視畫面中。
const tl = new ViewTimeline({
subject: document.getElementById('subject'),
});
如要將註解附加到網路動畫,請以 timeline
屬性的形式傳入,並省略任何 duration
(如果有的話)。您也可以選擇使用 rangeStart
和 rangeEnd
屬性傳入範圍資訊。
$el.animate({
opacity: [0, 1],
}, {
timeline: tl,
rangeStart: 'entry 25%',
rangeEnd: 'cover 50%',
});
✨ 親自體驗
更多體驗功能
以一組主要畫面格附加至多個檢視畫面時間軸範圍
一起來看看這份聯絡人清單的示範,上面的清單項目有動畫效果。當清單項目從底部滑入並淡出時,就會進入捲軸,當從頂端的捲軸退出 + 淡出的捲軸。
✨ 親自體驗
在本示範中,每個元素都會有一個檢視畫面時間軸,該時間軸追蹤該元素跨越捲軸的寬度,但同時有兩張捲動導向的動畫。animate-in
動畫會附加至時間軸的 entry
範圍,animate-out
動畫則附加至時間軸的 exit
範圍。
@keyframes animate-in {
0% { opacity: 0; transform: translateY(100%); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes animate-out {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-100%); }
}
#list-view li {
animation: animate-in linear forwards,
animate-out linear forwards;
animation-timeline: view();
animation-range: entry, exit;
}
您亦可建立一組含有範圍資訊的主要畫面格,而改為建立一組含有範圍資訊的主要畫面格,而不是放送兩個不同範圍的動畫。
@keyframes animate-in-and-out {
entry 0% {
opacity: 0; transform: translateY(100%);
}
entry 100% {
opacity: 1; transform: translateY(0);
}
exit 0% {
opacity: 1; transform: translateY(0);
}
exit 100% {
opacity: 0; transform: translateY(-100%);
}
}
#list-view li {
animation: linear animate-in-and-out;
animation-timeline: view();
}
由於主要畫面格包含範圍資訊,因此您不需要指定 animation-range
。傳回的結果與之前完全相同。
✨ 親自體驗
附加至非祖系捲動時間軸
名為「捲動時間軸」和「檢視時間軸」的查詢機制僅限於捲動祖系。但在多數情況下,需要動畫的元素並非需要追蹤的捲動器子項。
為完成此工作,啟用 timeline-scope
屬性。您可以使用這個屬性來宣告含有該名稱的時間軸,而不必實際建立。這樣時間軸上的名稱範圍就會擴大。在實務上,您將在共用父項元素上使用 timeline-scope
屬性,讓子項捲動器的時間軸附加至該元素。
例如:
.parent {
timeline-scope: --tl;
}
.parent .scroller {
scroll-timeline: --tl;
}
.parent .scroller ~ .subject {
animation: animate linear;
animation-timeline: --tl;
}
在這個程式碼片段中:
.parent
元素會宣告名為--tl
的時間軸。其任何子項都可以找到並用做animation-timeline
屬性的值。.scroller
元素實際定義名為--tl
的捲動時間軸。根據預設,只有子項可以查看此項目,但由於.parent
已設為scroll-timeline-root
,因此會附加至該物件。.subject
元素使用--tl
時間軸。它能行走其祖系樹狀結構,並在.parent
上找到--tl
。透過.parent
上的--tl
指向.scroller
的--tl
,.subject
基本上可以追蹤.scroller
的捲動進度時間軸。
換句話說,您可以使用 timeline-root
將時間軸移至祖系 (也稱為「提升」),讓祖系的所有子項都能存取該時間軸。
timeline-scope
屬性可同時與捲動時間軸和檢視畫面時間軸搭配使用。
更多示範與資源
本文討論的所有示範內容都列載於捲動式動畫.style mini-網站。這個網站包含更多示範,著重介紹捲動式動畫的功能。
另一個試聽版本就是這張專輯封面清單。每個封面都會以 3D 方向旋轉,因為中心會成為焦點。
✨ 親自體驗
或是使用 position: sticky
的堆疊資訊卡示範。隨著資訊卡堆疊,原本處於停滯狀態的資訊卡縮小,可以創造良好的深度效果。最後,整個堆疊分群會以群組的形式從檢視畫面中滑出。
✨ 親自體驗
scroll-based-animations.style 也介紹了一系列工具,例如本文前面的「查看時間軸範圍進度」圖表。
你也可以在 2023 年 Google I/O 大會的「網頁動畫新功能」中,找到以捲動為主的動畫。