具代表性的面試主題

前端面試題:如何用 CSS Scroll-driven Animations 設計可降級的滾動動效?

前端中等
Offer.cc 編輯團隊發佈 更新

題幹

請設計一個文章閱讀進度條和卡片入場動效,要求使用 CSS 滾動驅動時間軸,並說明滾動容器、view timeline、降級和無障礙處理。

1. 題目與適用場景

一個內容頁面需要頂部閱讀進度條,文章卡片進入滾動容器時還要淡入。團隊先用 scroll 事件監聽器和 requestAnimationFrame 實作,但在低階裝置上出現主執行緒抖動。請使用 CSS Scroll-driven Animations 重新設計,解釋 scroll progress 與 view progress 的差別,並說明瀏覽器不支援時的降級、reduced-motion 和真實驗證方法。

2. 面試官考察點

  • 是否理解滾動驅動動畫把 CSS 動畫綁定到 scroll timeline 或 view timeline,而非預設時間軸。
  • 是否能區分 scroll() 追蹤滾動容器進度與 view() 追蹤元素在最近滾動容器中的可見範圍。
  • 是否處理命名時間軸、動畫範圍、overflow 條件和動畫簡寫覆蓋順序。
  • 是否把相容性、效能、鍵盤閱讀、減少動效和無 JavaScript 降級納入方案。

3. 回答前需要釐清的問題

  1. 進度條跟隨整個文件,還是跟隨內部文章滾動容器?
  2. 卡片動畫應在進入視窗時播放一次,還是隨可見比例正向和反向變化?
  3. 目標瀏覽器是否允許使用 animation-timeline,舊瀏覽器需要什麼視覺和功能降級?
  4. reduced-motion 使用者是否仍要看到進度變化,以及動畫是否影響閱讀順序和焦點?

4. 30 秒回答框架

我會把閱讀進度條綁定到滾動容器的 scroll progress timeline,把卡片綁定到元素的 view progress timeline。scroll() 表示滾動位置從起點到終點的進度,view() 表示元素在最近滾動容器中進入和離開可視區域的範圍。動畫仍由 keyframes 定義,animation-timeline 決定進度來源;必須確保容器真的可滾動,並明確動畫範圍。舊瀏覽器保留靜態進度提示或使用節流後的輕量 JavaScript,不能讓核心內容依賴動畫。最後補上 supports、reduced-motion、鍵盤和低階裝置測試。

5. 分步驟深入解答

第一步:確認滾動時間軸的語意

scroll progress timeline 由滾動容器的滾動位置驅動,通常從起點的 0% 走到終點的 100%。view progress timeline 由目標元素相對於最近滾動容器的可見範圍驅動,適合卡片進入和離開視窗時同步動畫。兩者都建立在 CSS Animations 和 Web Animations API 的時間軸模型上。

第二步:實作閱讀進度條

先讓文章容器出現實際溢出,再命名滾動時間軸,把進度條的 animation-timeline 指向同名時間軸。不要只設定 animation-timeline 而忘記 keyframes;也不要把沒有滾動範圍的容器誤當成可用時間軸。

css
.article-scroller {
  overflow-y: auto;
  scroll-timeline-name: --article-scroll;
  scroll-timeline-axis: block;
}

.reading-progress {
  transform-origin: left center;
  animation: grow-progress linear;
  animation-timeline: --article-scroll;
}

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

第三步:實作卡片的 view timeline

卡片可以使用匿名 view(),由最近的祖先滾動容器追蹤它的可見範圍。需要更精確控制時,用 view-timeline-name 和 view-timeline-inset 命名時間軸並調整開始、結束邊界。動畫範圍應與設計稿中的可見區間一致,避免卡片剛碰到邊緣就完成全部動畫。

css
.card {
  animation: reveal-card linear both;
  animation-timeline: view(block 15% 15%);
}

@keyframes reveal-card {
  entry 0% { opacity: 0; transform: translateY(1rem); }
  cover 45% { opacity: 1; transform: translateY(0); }
  exit 100% { opacity: 0.6; }
}

第四步:處理動畫範圍與簡寫陷阱

命名時間軸和匿名函式都需要有正確的滾動容器關係;overflow、軸向和元素層級任一不匹配都可能讓時間軸沒有進度。設定 animation 簡寫後再設定 animation-timeline,因為簡寫會把未寫出的時間軸重設為預設值。對複雜進入、覆蓋和退出階段,使用 animation-range 或範圍關鍵影格表達意圖,並在目標瀏覽器實測。

第五步:設計降級與無障礙驗證

用 @supports 檢測 animation-timeline。增強規則不可用時,保留靜態進度條、正常內容順序和可讀對比度;若確需 JavaScript fallback,只在滾動事件中做節流後的低成本更新,避免重複執行布局。prefers-reduced-motion: reduce 下移除位移和連續動效,仍保留進度資訊和焦點順序。驗證鍵盤滾動、螢幕閱讀器、快速反向滾動、巢狀滾動容器和低階裝置幀率。

6. 高品質示範回答

我會把閱讀進度條綁定到文章滾動容器的 scroll progress timeline,把卡片綁定到最近滾動容器的 view progress timeline。前者表示滾動位置從 0% 到 100%,後者表示元素進入、覆蓋和離開可視區的範圍。實作時先確認 overflow 產生真實滾動距離,再設定 keyframes 和 animation-timeline,避免 animation 簡寫覆蓋時間軸。透過 @supports 提供靜態降級,只有必要時使用節流的 JavaScript;reduced-motion 下移除位移和連續動畫但保留內容、進度和焦點。最後用鍵盤、讀屏、巢狀滾動和低階裝置驗證。

7. 常見錯誤

  • 把 scroll() 和 view() 當成同一種進度 → 卡片無法按可見範圍變化 → 先說明滾動位置與元素可見範圍的差異。
  • 容器沒有溢出卻宣告 scroll timeline → 動畫沒有進度 → 檢查內容尺寸、overflow 和軸向。
  • 用 animation 簡寫覆蓋了 animation-timeline → 時間軸回到預設時間軸 → 簡寫後重新宣告 animation-timeline。
  • 只在現代瀏覽器顯示內容 → 舊瀏覽器功能缺失 → 保留靜態進度和正常閱讀順序。
  • reduced-motion 只把 opacity 設為零 → 內容回饋消失 → 拿掉位移和連續動效,保留語意和可見狀態。

8. 追問及應對

追問一:scroll timeline 和 view timeline 何時分別使用?

整個滾動區域的閱讀進度適合 scroll timeline;單個元素進入、覆蓋和離開滾動容器的動畫適合 view timeline。選擇依據是進度來源,而不是是否使用了 JavaScript。

追問二:為什麼宣告了 animation-timeline 卻沒有動畫?

常見原因是容器沒有實際滾動範圍、軸向不匹配、時間軸名稱不在祖先關係中,或 animation 簡寫後來把時間軸重設。應從布局溢出、時間軸關係和宣告順序逐項排查。

追問三:舊瀏覽器如何降級?

保留靜態進度提示和完整內容;若產品確實需要動態進度,再用節流的 scroll 監聽器做輕量更新。降級不應改變閱讀順序、鍵盤存取和焦點行為。

追問四:如何兼顧 reduced-motion?

在 prefers-reduced-motion: reduce 下移除位移、縮放和長時長動畫,保留進度條的靜態或瞬時更新、內容順序與可讀回饋,並驗證快速滾動不會遺失資訊。

公開來源

同類題目