使用捲動式動畫呈現捲動時的元素動畫

瞭解如何運用捲動時間軸和查看時間軸,以宣告方式建立捲動導向的動畫。

捲動式動畫

瀏覽器支援

  • 115
  • 115
  • x

資料來源

捲動式動畫是網路上常見的使用者體驗模式。捲動式動畫已連結至捲動容器的捲動位置。這表示當您向上或向下捲動時,連結的動畫會在直接回應中向前或向後移動。像是視差背景圖片,或閱讀指標會在你捲動時移動。

文件頂端的閱讀指標 (以捲動方式推動)。

捲動式動畫也是類似捲動式動畫,會與其捲動容器中元素位置相連結的動畫。舉例來說,元素在進入畫面中時可能會淡入。

在顯示圖片時,此頁面上的圖片會淡入。

如要達到這類效果,傳統的方法是在主執行緒上回應捲動事件,進而造成兩個主要問題:

  • 新式瀏覽器會在獨立程序上執行捲動,因此以非同步方式傳送捲動事件。
  • 主要執行緒動畫受到資源浪費

這使得建立成效卓越的捲動式動畫,與捲動操作不同步或非常困難。

自 Chrome 115 版起,系統會提供一組新的 API 和概念,可用於啟用宣告式捲動式動畫:捲動時間軸和查看時間軸。

這些新概念整合了現有的 Web Animations API (WAAPI)CSS Animation API,旨在沿用現有 API 帶來的優勢。包括讓捲動式動畫在主執行緒外執行。是的,我要正確讀起來:現在只需要新增幾行程式碼,就能建立流暢的動畫效果,像是捲動、在主執行緒中執行時等動作。不喜歡什麼地方?!

網路上的動畫、簡短回顧

使用 CSS 在網路上製作動畫

如要在 CSS 中建立動畫,請使用 at-rule 的 @keyframes 定義一組主要畫面格。使用 animation-name 屬性將其連結至元素,同時設定 animation-duration 以決定動畫所需時間。市面上還有更多 animation-* 長篇屬性,animation-easing-functionanimation-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 Animation API 可達到相同的效果。您可以建立新的 AnimationKeyFrameEffect 例項,或者使用較短的 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%。

捲動進度時間軸的視覺化呈現。向下捲動到捲軸底部時,進度值會從 0% 增加到 100%。

✨ 快來試試這項功能

捲動進度時間軸通常縮寫為「捲動時間軸」。

查看進度時間軸

這種時間軸類型會連結到捲動容器中特定元素的相對進度。如同捲動進度時間軸,系統會追蹤捲動的捲動偏移。與捲動進度時間軸不同的是,這是捲動器中主題的相對位置,用來決定進度。

這與 IntersectionObserver 的運作方式大致類似,可追蹤元素在捲動器的顯示程度。如果元素未顯示在捲動器中,則不會相交。如果它出現在捲軸內 (即使是最小的部分),就是相交。

檢視進度時間軸會從主體開始與捲動工具互動的那一刻開始,並在主體停止與捲動畫面互動時結束。在下方示意圖中,您可以看到當主體進入捲動容器,且在主體離開捲動容器時達到 100% 時,進度會從 0% 開始計數。

觀看進度時間軸的圖表。由於主旨 (綠色方塊) 跨越捲動器,進度會從 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() 的值為 rootblock。加總後,值為 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-namescroll-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 類別的新例項。使用想追蹤的 sourceaxis,傳入物業袋。

  • source:要追蹤的捲動器元素的參照。使用 document.documentElement 指定根捲動器。
  • axis:決定要追蹤的軸。與 CSS 變體類似,系統接受的值包括 blockinlinexy
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 會追蹤根捲動器,並在您捲動頁面時,將 #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();
}

Intermezzo:查看時間軸範圍

根據預設,與檢視畫面時間軸連結的動畫會附加至整個時間軸範圍。從拍攝主體進入捲動區域的那一刻開始,直到拍攝對象完全離開捲軸時結束。

您也可指定您想要附加的範圍,將其連結至檢視時間軸的特定部分。例如,只有在主題進入捲動器時才可使用此功能。在下方的示意圖中,當主體進入捲動容器,但從完全相交的當下達到 100% 時,進度會從 0% 開始計算。

檢視畫面時間軸,用於追蹤主體的進入範圍。只有在拍攝對象進入捲動區域時,動畫才會執行。

您可以指定的「查看時間軸」範圍如下:

  • 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 中,請使用 rangeStartrangeEnd 屬性。

$el.animate(
  keyframes,
  {
    timeline: tl,
    rangeStart: 'entry 0%',
    rangeEnd: 'entry 100%',
  }
);

請使用下方內嵌的工具,查看每個範圍名稱代表的意義,以及百分比對於起始與結束位置的影響。請嘗試將範圍起始值設為 entry 0%,並將範圍結束設為 cover 50%,然後拖曳捲軸來查看動畫結果。

如要查看時間軸範圍視覺化工具,請前往 https://goo.gle/view-timeline-range-tool

觀看錄影

您在使用這個檢視時間軸範圍工具時,可能會發現某些範圍可以用兩種不同的範圍名稱 + 範圍偏移組合來指定。舉例來說,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-nameview-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 類別的新例項。使用要追蹤的 subjectaxisinset 傳入資產包。

  • subject:想在其捲動器中追蹤的元素參照。
  • axis:要追蹤的軸。與 CSS 變體類似,系統接受的值包括 blockinlinexy
  • inset:判斷方塊是否處於可見狀態時,捲軸的插邊 (正數) 或輸出 (負) 調整項。
const tl = new ViewTimeline({
  subject: document.getElementById('subject'),
});

如要將元素附加至網頁動畫,請將其做為 timeline 屬性傳入,並省略任何 duration (如果有的話)。您也可以選擇使用 rangeStartrangeEnd 屬性傳入範圍資訊。

$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 迷你網站上的資訊。這個網站提供更多示範,可強調捲動式動畫的功能。

這份專輯封面清單就是另一個示範模式,每個封面都會以 3D 方式旋轉,成為中心焦點。

示範:封面流程

✨ 快來試試這項功能

或是此堆疊資訊卡示範使用 position: sticky。隨著資訊卡堆疊,卡住的資訊卡會縮小,產生良好的深度效果。最後,整個堆疊滑出畫面,

示範:堆疊資訊卡.

✨ 快來試試這項功能

此外, 捲動式動畫.style 也呈現了一系列工具,例如這篇文章先前提供的「查看時間軸範圍進度」視覺化內容。

在 2023 年 Google I/O 大會上,網頁動畫的新功能也會介紹捲動式動畫。