CSS Scroll-Driven Animations:純 CSS 實現捲動動畫
CSS Scroll-Driven Animations:純 CSS 實現捲動動畫
捲動觸發動畫長期以來是前端開發的「必須用 JavaScript」領域。Intersection Observer、GSAP ScrollTrigger、framer-motion 的 whileInView——這些工具幫助我們實現了許多精彩的捲動效果,但代價是 JavaScript 的執行成本和庫的體積。
現在,CSS Scroll-Driven Animations 規範的到來改變了這一切。
瀏覽器支援
截至 2025 年,支援情況如下:
- Chrome/Edge 115+:完整支援
- Safari 18+:支援(2024 年底加入)
- Firefox:部分支援,完整支援預計 2026 年
全球支援率約 75-80%,可以漸進增強的方式使用。
核心概念
CSS 動畫由兩個部分組成:
- 動畫定義(
@keyframes) - 時間軸(Timeline):決定動畫的進度如何隨時間推進
Scroll-Driven Animations 引入了兩種新的時間軸:
- Scroll Progress Timeline:以整個捲動容器的捲動進度為時間軸
- View Progress Timeline:以特定元素進入/離開視口的進度為時間軸
Scroll Progress Timeline:讀取進度條
最經典的應用:頁面頂部的「閱讀進度條」,完全不需要 JavaScript:
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(to right, #6366f1, #8b5cf6);
transform-origin: left center;
/* 以頁面捲動進度驅動動畫 */
animation: progress linear;
animation-timeline: scroll(root block);
}scroll() 函式接受兩個參數:
- 第一個:捲動容器(
root= 根元素,nearest= 最近的可捲動祖先) - 第二個:捲動軸(
block= 垂直,inline= 水平)
View Progress Timeline:元素進入視口動畫
這是更常見的需求:元素進入視口時觸發動畫。
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-on-scroll {
animation: fade-in-up linear both;
animation-timeline: view();
/* 在元素進入視口 20% 到 40% 時完成動畫 */
animation-range: entry 0% entry 40%;
}animation-range 控制動畫在哪個範圍內播放:
| 關鍵字 | 說明 |
|---|---|
entry |
元素進入視口的過程 |
exit |
元素離開視口的過程 |
cover |
元素完全覆蓋視口容器的過程 |
contain |
元素完全在視口內的過程 |
實用範例:卡片交錯進場
@keyframes card-enter {
from {
opacity: 0;
transform: translateY(60px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.card {
animation: card-enter ease-out both;
animation-timeline: view();
animation-range: entry 0% entry 50%;
}
/* 交錯效果:每張卡片稍微延遲 */
.card:nth-child(1) { animation-delay: calc(timeline-range-start * 0); }
.card:nth-child(2) { animation-delay: 0.1s; }
.card:nth-child(3) { animation-delay: 0.2s; }命名時間軸(Named Timelines)
對於複雜的場景,可以將捲動時間軸命名並跨元素使用:
/* 父容器定義命名時間軸 */
.hero-section {
scroll-timeline-name: --hero;
scroll-timeline-axis: block;
overflow: auto;
}
/* 子元素(或任何元素)使用這個時間軸 */
.hero-text {
animation: parallax linear;
animation-timeline: --hero;
}
.hero-bg {
animation: bg-parallax linear;
animation-timeline: --hero;
}
@keyframes parallax {
to { transform: translateY(-50px); }
}
@keyframes bg-parallax {
to { transform: scale(1.1) translateY(-20px); }
}視差捲動效果
.parallax-container {
view-timeline-name: --section;
view-timeline-axis: block;
}
.parallax-image {
animation: parallax-move linear both;
animation-timeline: --section;
animation-range: cover 0% cover 100%;
}
@keyframes parallax-move {
from { transform: translateY(-15%); }
to { transform: translateY(15%); }
}搭配 CSS 自訂屬性的進階技巧
/* 將捲動進度暴露為 CSS 變數,供其他屬性使用 */
@property --scroll-progress {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@keyframes set-progress {
to { --scroll-progress: 1; }
}
body {
animation: set-progress linear;
animation-timeline: scroll(root);
}
/* 其他元素可以讀取這個變數 */
.dynamic-color {
background: hsl(calc(var(--scroll-progress) * 360), 70%, 50%);
}降級與漸進增強
/* 降級:靜態版本 */
.animate-on-scroll {
opacity: 1;
transform: none;
}
/* 支援 scroll-driven animations 的瀏覽器 */
@supports (animation-timeline: view()) {
.animate-on-scroll {
animation: fade-in-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 40%;
}
}效能優勢
Scroll-Driven Animations 在瀏覽器的合成器執行緒(compositor thread)上運行,不需要佔用主執行緒的 JavaScript 時間。這意味著:
- 即使主執行緒繁忙,動畫仍然流暢
- 不需要監聽
scroll事件(高頻觸發) - 自動做到
will-change優化
實測對比:100 個元素同時做捲動動畫時,純 CSS 方案的 CPU 使用率約為 Intersection Observer + CSS 方案的 30%。
小結
CSS Scroll-Driven Animations 是近年來最令人興奮的 CSS 新功能之一。閱讀進度條、入場動畫、視差效果——這些曾經需要大量 JavaScript 的功能,現在可以用幾行 CSS 實現,而且效能更好。雖然 Firefox 的支援尚未完整,但採用漸進增強的策略,現在就可以開始使用。
分享這篇文章