Scrollytelling 是一種熱門技術,可用來打造引人入勝的沉浸式網頁體驗。這種技術會在使用者捲動頁面時,為頁面元素加上動畫效果,有效傳達故事或引導使用者瀏覽敘事內容。有了 CSS 捲動式動畫,您就能直接在 CSS 中建立這些效果,不必依賴 JavaScript。動畫是由捲動位置控制,而非時間,因此能確保動畫一律與使用者的捲動動作同步。
導入方式
如要建立捲動敘事體驗,您需要兩組元素:一組用於追蹤捲動位置,另一組用於動畫。
首先,請在要追蹤的元素上定義已命名的 view-timeline。這些會做為動畫的驅動程式。
#tracked {
section:nth-child(1){ view-timeline: --tl-1 block; }
section:nth-child(2){ view-timeline: --tl-2 block; }
section:nth-child(3){ view-timeline: --tl-3 block; }
section:nth-child(4){ view-timeline: --tl-4 block; }
section:nth-child(5){ view-timeline: --tl-5 block; }
}
接著,將動畫套用至要建立動畫的元素,並使用 animation-timeline 屬性將這些元素連結至您剛建立的時間軸。
#animated {
section {
animation: animate-in auto linear both, animate-out auto linear forwards;
animation-range: entry 25% cover 50%, exit 50% exit 75%;
}
section:nth-child(1){ animation-timeline: --tl-1; }
section:nth-child(2){ animation-timeline: --tl-2; }
section:nth-child(3){ animation-timeline: --tl-3; }
section:nth-child(4){ animation-timeline: --tl-4; }
section:nth-child(5){ animation-timeline: --tl-5; }
}
animation-timeline 必須與具名時間軸位於相同範圍,才能參照這些時間軸。您可以在共同祖系上使用 timeline-scope 屬性,讓所有需要時間軸的元素都能使用時間軸。:root 元素通常是合適的選擇。
html {
timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}
最後,您可以使用 animation-range 屬性,指定動畫應執行的確切時間軸範圍。這樣一來,您就能精細控管動畫的觸發時機和進度。
#animated section {
animation-range: entry 25% cover 50%, exit 50% exit 75%;
}
範例程式碼
html {
timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}
#tracked {
section:nth-child(1){ view-timeline: --tl-1 block; }
section:nth-child(2){ view-timeline: --tl-2 block; }
section:nth-child(3){ view-timeline: --tl-3 block; }
section:nth-child(4){ view-timeline: --tl-4 block; }
section:nth-child(5){ view-timeline: --tl-5 block; }
}
@keyframes animate-in {
from { scale: 0.5; opacity: 0; transform: rotateY(-180deg); }
to { transform: rotateY(0deg); }
}
@keyframes animate-out {
to { translate: 100% 0; opacity: 0; }
}
#animated {
section {
animation: animate-in auto linear both, animate-out auto linear forwards;
animation-range: entry 25% cover 50%, exit 50% exit 75%;
backface-visibility: hidden;
}
section:nth-child(1){ animation-timeline: --tl-1; }
section:nth-child(2){ animation-timeline: --tl-2; }
section:nth-child(3){ animation-timeline: --tl-3; }
section:nth-child(4){ animation-timeline: --tl-4; }
section:nth-child(5){ animation-timeline: --tl-5; }
}
/* MANDATORY Copy-Paste Safety: Disable continuous storytelling motion for sensitive users */
@media (prefers-reduced-motion: reduce) {
#animated section {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}
最佳做法
使用捲動驅動動畫時,請務必遵循幾項最佳做法,確保體驗流暢且無障礙:
- 務必加入功能偵測:並非所有瀏覽器都支援捲動驅動動畫。使用
@supports ((animation-timeline: scroll()) and (animation-range: 0% 100%))檢查支援情形,並為不支援的瀏覽器提供備用方案。- 這裡必須加入
(animation-range: 0% 100%)檢查,篩除僅部分支援的瀏覽器。 - 請勿使用
scroll-timeline-polyfill套件做為備用策略,因為該套件的功能不完整,且有許多已知問題。 - 如果動畫僅用於裝飾,請選擇漸進增強,且「不要」提供備用方案。
- 這裡必須加入
- 務必尊重使用者偏好:部分使用者偏好網頁上較少動態效果。使用
prefers-reduced-motion媒體查詢,為這些使用者停用或減少動畫。 - 建議只為高效能 CSS 屬性製作動畫:如要讓動畫最流暢,請只為瀏覽器合成器執行緒可處理的屬性製作動畫,例如
transform和opacity。為width或height等其他屬性建立動畫,可能會導致效能問題。 - 建議使用正確的宣告順序:使用
animation簡寫屬性時,請在之後宣告animation-timeline和animation-range,以免簡寫重設時間軸。
使用 view-timeline 屬性建立捲動驅動動畫時:
- 建議使用 CSS
<dashed-ident>做為名稱。 - 選用:明確指定要追蹤的軸。如果不是指定預設的
block軸 (例如在水平捲動器中),請使用view-timeline-axis明確指定要追蹤的軸。 - 務必確保查詢範圍有效:如果宣告
view-timeline的元素不是動畫元素的平面樹狀結構祖系,請在共用祖系上使用timeline-scope,提高view-timeline名稱的瀏覽權限。
備用策略
如果瀏覽器不支援捲動驅動動畫,可以使用備用方案重新建立視覺效果。備用方案通常是使用捲動事件監聽器 (用於 ScrollTimeline 效果) 或 IntersectionObserver API (用於 ViewTimeline 效果) 建構。
如果瀏覽器內建支援捲動式動畫,請一律使用原生 CSS 實作,因為這類實作的效能較佳。
請注意,並非所有效果都能使用備用方法重新建立。
針對這個用途,下列指令碼會為不支援捲動驅動動畫的瀏覽器套用備用方案。它會使用 IntersectionObserver 追蹤每個 #tracked section 元素的可見度,並據此更新對應 #animated section 的 transform 屬性。
const animatedSections = document.querySelectorAll('#animated section');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const sectionIndex = Array.from(document.querySelectorAll('#tracked section')).indexOf(entry.target);
if (sectionIndex !== -1) {
const animatedSection = animatedSections[sectionIndex];
const ratio = entry.intersectionRatio;
// Animate-in
animatedSection.style.opacity = ratio;
animatedSection.style.transform = `scale(${0.5 + ratio * 0.5}) rotateY(${-180 + ratio * 180}deg)`;
// Animate-out
if (ratio < 0.5) {
animatedSection.style.translate = `${(0.5 - ratio) * 2 * 100}% 0`;
} else {
animatedSection.style.translate = '0 0';
}
}
});
}, { threshold: Array.from({length: 101}, (_, i) => i / 100) });
document.querySelectorAll('#tracked section').forEach(section => {
observer.observe(section);
});
以及隨附的 CSS:
#animated section {
opacity: 0;
transform: scale(0.5) rotateY(-180deg);
backface-visibility: hidden;
}
/* MANDATORY Copy-Paste Safety: Ensure content remains fully visible and legible for assistive technologies or users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
#animated section {
opacity: 1 !important;
transform: none !important;
translate: 0 0 !important;
}
}
對於不支援原生 CSS 功能的瀏覽器,這個備用方案可提供更準確的捲動驅動動畫,確保所有使用者都能享有更一致的體驗。只要為 IntersectionObserver 使用一系列門檻,就能更精確地追蹤捲動位置,並建立更流暢的動畫。