瞭解如何使用捲動時間軸和 View 時間軸,以宣告式方式建立捲動驅動動畫。
捲動驅動動畫
捲動驅動動畫是網路上常見的使用者體驗模式。捲動驅動動畫會連結至捲動容器的捲動位置。也就是說,當您向上或向下捲動時,連結的動畫會直接回應,向前或向後快速掃過。例如視差背景圖片或捲動時會移動的閱讀指標。
類似的捲動驅動動畫類型,是指與元素在捲動容器中的位置相關聯的動畫。舉例來說,您可以使用此屬性,讓元素在進入畫面時淡入。
實現這類效果的傳統方法,是在主執行緒上回應捲動事件,但這會導致兩個主要問題:
- 新型瀏覽器會在獨立程序中執行捲動作業,因此會以非同步方式傳送捲動事件。
- 主執行緒動畫可能會發生卡頓。
因此,要建立與捲動同步的效能良好捲動驅動動畫,幾乎是不可能或非常困難。
從 Chrome 115 版開始,您可以使用一組新的 API 和概念來啟用宣告式捲動驅動動畫:捲動時間軸和檢視時間軸。
這些新概念可與現有的 Web Animations API (WAAPI) 和 CSS Animations API 整合,繼承這些現有 API 帶來的優點。包括讓捲動驅動動畫在主執行緒外執行的功能。沒錯,您現在只需加入幾行額外的程式碼,就能享有流暢的動畫,並由捲動操作在主執行緒中執行。這不是很棒嗎?
網頁上的動畫,簡短回顧
使用 CSS 製作網頁動畫
如要在 CSS 中建立動畫,請使用 @keyframes
at-rule 定義一組主要畫面格。請使用 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 開始,並隨著時鐘時間的推移而向前跳動。這是預設的動畫時間軸,也是您目前唯一可存取的動畫時間軸。
捲動驅動動畫規格定義了兩種可用時間軸:
- 捲動進度時間軸:與捲動容器沿著特定軸線的捲動位置相關聯的時間軸。
- 檢視進度時間軸:與特定元素在其捲動容器中的相對位置相關聯的時間軸。
捲動進度時間軸
捲動進度時間軸是動畫時間軸,與捲動容器 (也稱為 scrollport 或 scroller) 的捲動位置進度相關聯,沿著特定軸線運作。將捲動範圍中的某個位置轉換為進度百分比。
起始捲動位置代表 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)
。
示範:閱讀進度指標
這個示範的閱讀進度指標會固定在可視區域頂端。向下捲動頁面時,進度列會隨著捲動而變大,直到到達文件結尾並佔滿可視區域的寬度為止。使用匿名的捲動進度時間軸來驅動動畫。
✨ 歡迎試用
閱讀進度指標會使用固定位置,位於頁面頂端。如要使用組合動畫,請使用 transform
將元素在 x 軸上縮小,而非讓 width
播放動畫。
<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% 開始,表示您目前正在查看三張圖片中的第 1 張。當最後一張圖片顯示在檢視畫面上時 (這會由捲軸捲動到結尾所決定),指標會占據捲軸的整個寬度。使用名為 Scroll Progress Timeline 的時間軸來驅動動畫。
✨ 歡迎試用
圖庫的基本標記如下:
<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,
});
如要將其附加至 Web Animation,請將其傳遞為 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();
}
插曲:查看時間軸範圍
根據預設,連結至 View Timeline 的動畫會附加至整個時間軸範圍。這項測試會從主體即將進入捲動區域的瞬間開始,並在主體完全離開捲動區域時結束。
您也可以指定要附加的範圍,將圖表連結至「檢視時間軸」的特定部分。例如,只有在主體進入捲軸時才會發生這種情況。在下列示意圖中,當主體進入捲動容器時,進度會從 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 中建立 View 時間軸,請建立 ViewTimeline
類別的新例項。傳入資源袋,其中包含要追蹤的 subject
、axis
和 inset
。
subject
:您要在其專屬捲軸中追蹤的元素參照。axis
:要追蹤的軸。與 CSS 變化版本類似,可接受的值為block
、inline
、x
和y
。inset
:在判斷方塊是否在檢視區域內時,對捲動區域進行內嵌 (正值) 或外嵌 (負值) 調整。
const tl = new ViewTimeline({
subject: document.getElementById('subject'),
});
如要將其附加至 Web Animation,請將其傳遞為 timeline
屬性,並省略任何 duration
(如果有)。您可以選擇使用 rangeStart
和 rangeEnd
屬性傳入範圍資訊。
$el.animate({
opacity: [0, 1],
}, {
timeline: tl,
rangeStart: 'entry 25%',
rangeEnd: 'cover 50%',
});
✨ 歡迎試用
更多值得一試的操作
使用一組主要畫面格附加至多個 View 時間軸範圍
請參考這份聯絡人清單示範,其中的清單項目會顯示動畫效果。清單項目從底部進入捲動區時,會以滑動+淡入的方式進入;從頂端離開捲動區時,則會以滑動+淡出的方式離開。
✨ 歡迎試用
在這個示範中,每個元素都會加上一個 View 時間軸,用於追蹤元素在跨越捲動區時的狀態,但會附加兩個捲動驅動動畫。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
,因此會附加到scroll-timeline-root
。.subject
元素會使用--tl
時間軸。它會向上瀏覽祖系樹狀結構,並在.parent
上找到--tl
。.parent
上的--tl
指向.scroller
的--tl
,.subject
基本上會追蹤.scroller
的捲動進度時間軸。
換句話說,您可以使用 timeline-root
將時間軸移至祖系 (又稱為提升),讓祖系的所有子項都能存取。
timeline-scope
屬性可搭配捲動時間軸和檢視時間軸使用。
更多示範和資源
本文所述的所有範例皆可在scroll-driven-animations.style 迷你網站中找到。該網站提供更多範例,說明捲動驅動動畫的各種可能性。
這份專輯封面清單就是其中一個額外示範。每個封面都會以 3D 形式旋轉,並在中央亮相。
✨ 歡迎試用
或者,您也可以參考這個運用 position: sticky
的卡片堆疊示範。當資訊卡堆疊時,已固定的資訊卡會縮小,產生漂亮的深度效果。最後,整個資料疊代會以群組形式滑出畫面。
✨ 歡迎試用
scroll-driven-animations.style 也提供一系列工具,例如這篇文章稍早提到的「View Timeline Range Progress」圖表。
在 2023 年 Google I/O 大會的「Web Animations 最新功能」中,我們也將介紹捲動驅動動畫。