CSS 捲動驅動動畫:零 JS 實作視差捲動、進度條和揭示效果

前端工程(更新於 2026年6月2日)

告別 scroll 事件監聽

傳統捲動動畫依賴 JS 監聽 scroll 事件:

// ❌ 傳統方式:JS 監聽 + 強制同步佈局
window.addEventListener('scroll', () => {
  const progress = window.scrollY / (document.body.scrollHeight - window.innerHeight);
  progressBar.style.width = `${progress * 100}%`;
  // 每次 scroll 都觸發重排!
});

CSS 捲動驅動動畫讓這一切用純 CSS 完成,執行在合成執行緒,不阻塞主執行緒:

/* ✅ 純 CSS:閱讀進度條 */
.progress-bar {
  width: 100%;
  height: 3px;
  background: blue;
  transform-origin: left;
  animation: progress linear;
  animation-timeline: scroll();
}

@keyframes progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

scroll-timeline 語法

匿名捲動時間線

/* 基於最近可捲動祖先的捲動 */
.animated {
  animation-timeline: scroll();
}

/* 指定方向 */
.animated-x {
  animation-timeline: scroll(x); /* 水平捲動 */
}

.animated-y {
  animation-timeline: scroll(y); /* 垂直捲動 */
}

命名捲動時間線

/* 在捲動容器上宣告時間線名稱 */
.scroll-container {
  scroll-timeline: --page-scroll y;
  overflow-y: auto;
}

/* 在動畫元素上引用 */
.animated-element {
  animation: fade-in linear;
  animation-timeline: --page-scroll;
}

view-timeline 語法

view-timeline 基於元素進入/離開視口的進度:

.reveal-item {
  view-timeline: --item-reveal;
  animation: reveal linear both;
  animation-timeline: --item-reveal;
}

@keyframes reveal {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

view-timeline-range:精確控制動畫範圍

.reveal-item {
  view-timeline: --item-reveal;
  animation: reveal linear both;
  animation-timeline: --item-reveal;
  /* 只在元素進入視口的前 30% 播放動畫 */
  animation-range: entry 0% entry 30%;
}
range 值 含義
entry 元素開始進入視口
exit 元素開始離開視口
cover 元素完全在視口內
contain 元素從完全進入到開始離開
entry-crossing 元素正在穿越視口邊緣進入

實戰一:視差捲動

.parallax-container {
  overflow-y: auto;
  scroll-timeline: --parallax y;
  height: 100vh;
}

.parallax-bg {
  animation: parallax-move linear;
  animation-timeline: --parallax;
}

@keyframes parallax-move {
  from { transform: translateY(0); }
  to { transform: translateY(-200px); }
}

/* 不同深度的視差速度 */
.parallax-layer-1 { animation: parallax-move-slow linear; animation-timeline: --parallax; }
.parallax-layer-2 { animation: parallax-move-medium linear; animation-timeline: --parallax; }
.parallax-layer-3 { animation: parallax-move-fast linear; animation-timeline: --parallax; }

@keyframes parallax-move-slow { to { transform: translateY(-50px); } }
@keyframes parallax-move-medium { to { transform: translateY(-150px); } }
@keyframes parallax-move-fast { to { transform: translateY(-300px); } }

實戰二:閱讀進度條

/* 頁面頂部的閱讀進度指示器 */
.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: linear-gradient(90deg, #3b82f6, #8b5cf6);
  transform-origin: left;
  z-index: 1000;
  animation: reading-progress linear;
  animation-timeline: scroll(root y);
}

@keyframes reading-progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

實戰三:捲動揭示(Scroll Reveal)

/* 從下方淡入 */
.reveal-up {
  view-timeline: --reveal;
  animation: reveal-up linear both;
  animation-timeline: --reveal;
  animation-range: entry 0% entry 40%;
}

@keyframes reveal-up {
  from {
    opacity: 0;
    transform: translateY(80px) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* 從左側滑入 */
.reveal-left {
  view-timeline: --reveal;
  animation: reveal-left linear both;
  animation-timeline: --reveal;
  animation-range: entry 0% entry 30%;
}

@keyframes reveal-left {
  from {
    opacity: 0;
    transform: translateX(-60px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

交錯延遲

/* 用 animation-delay 實作交錯效果 */
.stagger-item:nth-child(1) { animation-delay: 0ms; }
.stagger-item:nth-child(2) { animation-delay: 80ms; }
.stagger-item:nth-child(3) { animation-delay: 160ms; }
.stagger-item:nth-child(4) { animation-delay: 240ms; }
.stagger-item:nth-child(5) { animation-delay: 320ms; }

實戰四:水平捲動區域

.horizontal-scroll {
  overflow-x: auto;
  scroll-timeline: --hscroll x;
}

.card {
  animation: card-appear linear both;
  animation-timeline: --hscroll;
  animation-range: entry 0% entry 20%;
}

@keyframes card-appear {
  from {
    opacity: 0;
    transform: scale(0.9);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

實戰五:粘性頭部收縮

.page {
  scroll-timeline: --page y;
  overflow-y: auto;
}

.sticky-header {
  position: sticky;
  top: 0;
  animation: header-shrink linear both;
  animation-timeline: --page;
  animation-range: 0px 100px;
}

@keyframes header-shrink {
  from {
    padding: 24px;
    font-size: 2rem;
  }
  to {
    padding: 12px;
    font-size: 1.25rem;
  }
}

實戰六:圖片序列幀動畫

.sprite-container {
  scroll-timeline: --sprite y;
  overflow-y: auto;
}

.sprite-image {
  animation: sprite-steps steps(24) both;
  animation-timeline: --sprite;
}

@keyframes sprite-steps {
  from { background-position: 0 0; }
  to { background-position: -2400px 0; } /* 24幀 × 100px寬 */
}

JS vs CSS 捲動動畫對比

維度 JS (scroll 事件) CSS scroll-timeline
效能 主執行緒,可能卡頓 合成執行緒,60fps
程式碼量 20-50 行 5-10 行
相容性 全部瀏覽器 Chrome 115+, Firefox 125+
靈活性 任意邏輯 線性/分段動畫
除錯 console.log DevTools Animations 面板
記憶體 需手動清理監聽器 自動管理

Polyfill 策略

// 檢測支援
const supportsScrollTimeline = CSS.supports('animation-timeline', 'scroll()');

if (!supportsScrollTimeline) {
  // 降級到 IntersectionObserver + CSS 類別切換
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      entry.target.classList.toggle('in-view', entry.isIntersecting);
    });
  }, { threshold: 0.2 });

  document.querySelectorAll('.reveal-item').forEach(el => {
    observer.observe(el);
  });
}

瀏覽器相容性

瀏覽器 scroll-timeline view-timeline animation-range
Chrome 115+
Edge 115+
Firefox 125+
Safari 17.4+

Safari 仍在實作中,建議使用特性檢測 + IntersectionObserver 降級。


最佳實踐

  1. 優先使用 view-timeline:大多數捲動揭示效果用 view-timelinescroll-timeline 更簡潔
  2. animation-range 精確控制:避免動畫在不可見時仍在執行
  3. will-change 配合:對動畫元素添加 will-change: transform, opacity
  4. 減少同時動畫元素:超過 20 個同時執行的捲動動畫可能影響效能
  5. 特性檢測降級:Safari 不支援時用 IntersectionObserver 降級

總結

CSS 捲動驅動動畫是 2026 年前端最令人興奮的新特性之一。它讓視差捲動、閱讀進度條、捲動揭示等經典效果從 JS 回歸 CSS,效能提升顯著(合成執行緒執行,不阻塞主執行緒)。雖然 Safari 支援尚不完善,但配合 IntersectionObserver 降級,現在就可以在生產環境使用。

本站提供瀏覽器本地工具,免註冊即可試用 →

#滚动驱动动画#CSS动画#scroll-timeline#视差滚动#无JS动画