瞭解如何使用捲動時間軸和檢視區塊時間軸,以宣告式方式建立捲動驅動的動畫。
發布日期:2023 年 5 月 5 日
捲動驅動動畫
捲動驅動動畫是網路上常見的使用者體驗模式。捲動驅動動畫會連結至捲動容器的捲動位置。也就是說,當您向上或向下捲動時,連結的動畫會直接回應,向前或向後清除。例如視差背景圖片或捲動時移動的閱讀指標等效果。
另一種捲動驅動動畫是連結至元素在捲動容器中的位置。舉例來說,元素可隨著進入檢視畫面而淡入。
如要達成這類效果,傳統做法是回應主要執行緒上的捲動事件,但這會導致兩個主要問題:
- 新式瀏覽器會在獨立程序中執行捲動作業,因此會以非同步方式傳送捲動事件。
- 主執行緒動畫容易發生延遲。
因此,要建立與捲動同步的高效捲動驅動動畫,幾乎是不可能或非常困難。
從 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 就會產生動畫。方法是參照名為「Scroll Progress Timeline」的 --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 會追蹤根捲軸,並在您捲動頁面時,將 #progress 在 X 軸上從 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();
}
中場休息:查看時間軸範圍
根據預設,連結至檢視時間軸的動畫會附加至整個時間軸範圍。從主體即將進入捲動埠開始,到主體完全離開捲動埠為止。
您也可以指定要附加的範圍,將其連結至「檢視時間軸」的特定部分。舉例來說,只有在主體進入捲動器時,在下方的視覺化圖片中,當主體進入捲動容器時,進度會從 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'),
});
如要將其附加至 Web Animation,請以 timeline 屬性傳入,並省略任何 duration (如有)。您也可以使用 rangeStart 和 rangeEnd 屬性傳遞範圍資訊。
$el.animate({
opacity: [0, 1],
}, {
timeline: tl,
rangeStart: 'entry 25%',
rangeEnd: 'cover 50%',
});
✨ 親自體驗
更多值得一試的操作
使用一組主要畫面格附加至多個「檢視時間軸」範圍
讓我們看看這個聯絡人清單的示範,清單項目會以動畫呈現。清單項目從底部進入捲動埠時,會滑入並淡入;從頂端離開捲動埠時,則會滑出並淡出。
✨ 親自體驗
在本示範中,每個元素都會以一個 View Timeline 裝飾,追蹤元素跨越捲動埠的過程,但會附加兩個捲動驅動動畫。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。當--tl指向.scroller的--tl時,.subject基本上會追蹤.scroller的捲動進度時間軸。.parent
換句話說,您可以使用 timeline-root 將時間軸上移至祖先 (又稱「提升」),讓祖先的所有子項都能存取時間軸。
timeline-scope 屬性可搭配捲動時間軸和檢視時間軸使用。
更多示範和資源
本文涵蓋的所有示範都位於scroll-driven-animations.style 迷你網站。這個網站還提供許多其他範例,讓您瞭解捲動驅動動畫的各種可能性。
其中一個額外範例是這份專輯封面清單。每張封面都會以 3D 旋轉,成為畫面焦點。
✨ 親自體驗
或是這個運用 position: sticky 的堆疊式資訊卡範例。資訊卡堆疊時,已固定的資訊卡會縮小,營造出漂亮的景深效果。最後,整個堆疊會以群組形式滑出畫面。
✨ 親自體驗
scroll-driven-animations.style 也提供一系列工具,例如本文稍早介紹的「檢視時間軸範圍進度」視覺化工具。
Google I/O 2023 的「Web Animations 最新消息」也介紹了捲動驅動動畫。