TechMoon 科技月球
WordPress、SEO 與 AI 工具實測指南
TechMoon 科技月球
WordPress、SEO 與 AI 工具實測指南

CSS 捲動驅動動畫在 2026 年 10 月跨進主流瀏覽器:scroll() 把動畫綁在捲動容器的進度上,view() 綁在元素進入可視範圍的進度上。本文用一個實際跑過的示範頁拆解兩種時間軸的差別、animation 簡寫會重設 timeline 的宣告順序陷阱,以及產出後的三步驗收。
用 AI 摘要這篇文章:
「滑到這裡才動」的網頁效果,背後通常是兩種不同的需求:閱讀進度條跟著整份文件的捲動位置慢慢填滿,或者某張卡片進入畫面後才開始淡入。兩種效果現在都可以只靠 CSS 完成,不必寫 JavaScript 去監聽捲動事件,關鍵是 animation-timeline 屬性搭配 scroll() 與 view() 兩個函式。
為了確認行為,我做了一個單檔示範頁實際跑過:頂端一條綠色進度條,正文五張卡片。在 Chromium 的 1200×800 視窗裡實測,頁面可捲距離是 836 像素,捲到 401 像素時進度條的縮放值是 0.4797,正好等於 401 除以 836;捲到 786 像素時是 0.9402,一樣等於 786 除以 836。卡片的透明度則跟著自己落在畫面裡的位置漸進變化,往回捲,動畫就跟著倒轉。這些是從瀏覽器讀出來的實際樣式值,可以照同樣的方式再量一次。

時效背景值得先講:MDN 目前把這兩個函式標記為 Baseline 2026、Newly available,意思是 2026 年 10 月起,最新版的 Chrome、Edge、Firefox、Safari 都支援了。Chrome 用戶其實從 115 版(2023 年)就能用,等 Safari 26 與 Firefox 159 補上之後,整個主流生態才算湊齊。對大多數網站來說,現在是把它寫進正式專案的合理時間點。
這篇的路線:先分清兩種時間軸的計算依據,再把需求寫成四欄規格,照示範頁的程式碼實作,最後用三個動作驗收。無論你自己寫,還是把需求交給 AI 產出再驗收,規格的寫法都一樣,而規格裡最重要的那一欄,恰恰是最常被省略的。
CSS 動畫的進度本來跟時間走:文件載入後經過幾秒,動畫就走到幾趴。animation-timeline 做的事是把這條時間軸換掉,換成別的東西。scroll() 與 view() 各換上一種新來源,這也是兩者唯一需要記住的差別。
scroll() 建立的是捲動進度時間軸:動畫的進度取決於某個捲動容器捲到哪裡。容器有三種選擇,nearest 是離動畫元素最近的可捲動祖先(預設值)、root 是整份文件、self 是元素自己;軸向有 block(預設)、inline、x、y 四種,一般頁面用 block,橫向捲動的輪播或時間軸就換 inline 或 x。所以 scroll()、scroll(block)、scroll(nearest)、scroll(block nearest)、scroll(nearest block) 五種寫法完全等價,MDN 的文件把它們並列在同一段說明裡。要特別記得的失效條件:如果指定的軸上沒有捲動條,這條時間軸不會活化,動畫會停在起點。
還有一個實務取向的提醒:示範頁的進度條用 position: fixed 固定在頁頂,這種元素直接寫 scroll(root block) 指名整份文件最省事,不必依賴「最近的捲動祖先是誰」這層繼承關係的推論。規格裡把容器寫死,比讓它落在預設值上更好驗收。
view() 建立的是可視進度時間軸:動畫的進度取決於某個元素自己在捲動容器裡的能見程度。文件對 0% 與 100% 的定義很具體:0% 是元素的邊緣剛在捲動容器一側冒出來,100% 是元素的外緣觸到容器的對側邊緣。view() 也可以帶 inset 參數(最多兩個值,可以是 auto 或長度與百分比,正值讓判定範圍往內縮、負值往外擴),把這兩個觸發點往中間收,延後開始、提早結束。軸向預設同樣是 block。

有一個常見誤解要先畫開:「滑到就播一次」不等於 view()。view() 是連續的進度軸,元素在畫面裡的位置決定動畫走到哪,往回捲就往回走;「只播一次、播完就停在結尾」是另一種行為要求,得靠動畫播放次數之類的設定另外處理。分不清這兩者,驗收時就會把正確的產出誤判成錯的。另外,動畫的對象不必是那個進場元素本身:想讓 A 元素的動畫跟著 B 元素的可視進度走,要先用 view-timeline-name 在 B 元素本身取名(捲動進度則用 scroll-timeline-name 加在捲動容器上),再讓 A 元素的 animation-timeline 引用這個名字,這叫具名時間軸。
至於為什麼值得從 JavaScript 換過來,Chrome 官方教學文的說法(2023 年 5 月發布)是:傳統做法監聽捲動事件,而現代瀏覽器的捲動跑在另一個行程,事件非同步送達,加上主執行緒的動畫容易卡頓,兩者相加很難做出跟手的捲動效果;捲動驅動動畫改由瀏覽器直接隨捲動位置驅動。這是官方文章的主張,本文沒有做效能實測,但「事件追不上捲動」的困境是前端圈熟悉的老問題。
MDN 的 Baseline 標記寫得很白話:2026 年 10 月起,這個功能在最新版的主流瀏覽器都能用,但舊裝置與舊版本可能不行。對應到版本,MDN 相容表與瀏覽器相容資料(BCD)記錄的都是同一組:Chrome 115、Edge 115、Firefox 159、Safari 26,行動版同梯(Chrome Android 115、Firefox Android 159、Safari iOS 26)。

把時間軸拉長看:Chrome 官方教學文發布於 2023 年 5 月,也就是說 Chromium 家族獨享了三年多,Safari 26 與 Firefox 159 上線之後,MDN 才給出 Baseline 2026 的標記。這三年差異解釋了為什麼網路上的教學與範例品質參差:很多是只有 Chrome 能跑的年代的產物,抄舊範例之前,先確認它有沒有把其他瀏覽器考慮進去。

Baseline 標記的但書也要照單全收。它的定義是「每個 Baseline 瀏覽器的最新穩定版都支援」,要升級成 widely available 還得再等各瀏覽器支援滿 2.5 年;系統 WebView、舊裝置、輔助技術的實際表現都不在保證範圍,MDN 也明文說 Baseline 不能取代實際測試。各瀏覽器版本在真實使用者間的滲透狀況,本文沒有查證,所以下面退場規則不能省:可以開始用,但交付時要連同不支援時的畫面一起交。
看過走鐘的捲動動畫,病因多半出在規格漏了「動畫跟誰的進度走」這一欄,於是實作的人自己挑了一種。把需求寫滿四欄,猜測空間就收掉了:
| 規格欄位 | 閱讀進度條(示範頁的例子) | 卡片進場(示範頁的例子) |
|---|---|---|
| 動畫跟誰的進度走 | 捲動容器的捲動進度(scroll()) | 元素自己進入可視範圍的進度(view()) |
| 對象與軸向 | scroll(root block):整份文件、垂直軸 | view():最近的捲動容器、預設 block 軸 |
| 起點與終點 | 頂端是 0%、底端是 100%,全程對應 | 邊緣初現是 0%、外緣碰到對側是 100% |
| 失效條件 | 指定軸沒有捲動條就停住 | 指定軸沒有捲動條,進度停在零 |
| 退場畫面 | 不支援時隱藏進度條 | 不支援時內容直接完整可見 |
套用到自己的需求,把它寫成一段話就夠,以示範頁為例:「閱讀進度條跟整份文件的垂直捲動進度同步,捲到底正好填滿;每張卡片在自己進入視窗後才開始淡入;瀏覽器不支援這組功能、或使用者設定減少動態效果時,全部內容直接顯示。」四欄全釘死,誰來實作都一樣。反過來說,如果需求只寫「做個滑動效果」,實作的人就得替你決定時間軸來源、對象與退場,每一項都可能跟你想的不同。
要補充的是兩個函式的分工界線:進度條、視差捲動、隨捲動改變的導覽指示,計算依據是容器的捲動位置,用 scroll();元素進場的淡入、平移、變形,計算依據是元素自己的能見度,用 view()。同一頁混合兩者很常見,示範頁就是一條 scroll() 進度條加五張 view() 卡片。
實作階段有一個文件明文、卻最容易踩的陷阱:animation 簡寫會把 animation-timeline 重設回預設值 auto,而且這個屬性在簡寫裡不能指定內容(MDN 稱它為 reset-only value)。後果是樣式看起來都對、動畫卻不動。解法照 MDN 的建議:animation-timeline 要宣告在 animation 之後,或者用更高的優先權壓過簡寫。
跨瀏覽器有個小補丁:MDN 在 scroll() 範例的 animation-duration: 1ms 旁註明,Firefox 需要非零的 duration 才會套用動畫。照抄這行就可以,在捲動時間軸下這個數值不會影響動畫速度。另外 animation-timeline 支援逗號清單,多個動畫時按 animation-name 的順序一一對應,多出來的時間軸會被忽略。
示範頁進度條的部分長這樣(簡化後):#progress { position: fixed; animation: grow 1ms linear; animation-timeline: scroll(root block); },搭配 @keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }。注意 animation-timeline 刻意寫在 animation 下一行,順序調換就會被簡寫重設。卡片的進場同樣兩行:animation: reveal 1ms linear both; animation-timeline: view();。

退場層在示範頁有兩個成員。@supports not (animation-timeline: scroll()) 成立時,把進度條藏起來、動畫整組停用,內容直接可見;@media (prefers-reduced-motion: reduce) 則回應使用者作業系統的減少動態設定,同樣停用動畫。第二個條件是無障礙設計的慣例:動畫是加分題,內容與舒適度才是本體,兩個條件寫在同一份樣式表裡,交付時就不用再分版本維護。
慢滑全程對應進度。從頁頂慢慢滑到底,進度條應該連續跟著走,中間沒有跳段。要量的話用示範頁的方法:讀出視窗捲動位置與進度條的縮放值,捲動位置除以可捲距離就是期望值。我在 836 像素可捲距離下量到 401 像素對 0.4797、786 像素對 0.9402,完全吻合;如果量出來的值跟捲動百分比對不上,代表時間軸的對象或軸向跟規格不一致。

往回捲要倒轉。view() 的卡片往回捲應該變回半透明,進度條往回縮。這是時間軸驅動的特徵:動畫位置等於進度,進度回頭動畫就回頭。如果效果只前進不倒退,多半不是用時間軸做的,行為跟規格不符,該退回重做而不是收貨。
關掉動畫驗內容。退場層存在的理由:寧可沒有動畫,不能丟掉內容。驗法是把動畫宣告暫時停用(等同不支援的環境),確認畫面仍然完整可讀。示範頁實測的結果:停用動畫層之後,五張卡片的透明度全部是 1、進度條隱藏,內容一個字都不少。淡入類的動畫尤其要過這關,因為它的起始狀態是看不見,動畫一旦沒跑起來,內容就整段消失。

動畫完全不動、內容看得到:依序查三個地方。先看宣告順序,animation-timeline 是不是被 animation 簡寫蓋掉了;再看指定的軸上有沒有捲動條,沒有捲動條時間軸不活化;最後對版本表,確認瀏覽器在支援清單裡。
內容整段看不到:淡入動畫把初始透明度設為零,而動畫沒有跑起來,兩個條件相加就是整段空白。先照上面的順序把動畫救活;救不活的環境就靠退場層讓內容直接可見。這也是規格裡「退場畫面」那一欄存在的目的。
Chrome 會動、Firefox 不動:查 animation-duration 是不是零,Firefox 需要非零值才套用動畫,照 MDN 範例補上 1ms 即可。
動畫會動,但起訖不對:卡片太早開始淡入、或還沒完全進畫面就定格,問題出在觸發點而不是時間軸來源。view() 的 inset 參數可以把 0% 與 100% 的判定範圍往內縮(例如 view(20% 20%)),讓動畫在元素更深入畫面後才開始、更早收尾。改規格的「起點與終點」那一欄,比調 keyframe 的百分比更接近病因。
退場層同時是還原鍵。真的要放棄這組效果,把 animation 與 animation-timeline 兩段宣告拿掉,@supports 條件會讓不支援的瀏覽器本來就走無動畫版面,內容與版位不變,隨時可以再加回來。
買到的是跨瀏覽器的起跑線:最新穩定版的 Chrome、Edge、Firefox、Safari 都支援,內部工具、個人網站、內容網站的進場效果與閱讀進度條都可以放心用純 CSS 做,不必為了跨瀏覽器再掛一套 JavaScript 動畫函式庫。沒買到的也要列清楚:舊裝置與舊版本的保證、系統 WebView 的表現、輔助技術的相容測試,以及任何效能保證。widely available 的門檻是各瀏覽器支援滿 2.5 年,在那之前,版本但書會一直存在。
要對大眾流量交付的話,用自己的使用者瀏覽器分佈做判斷,不要把 Baseline 標記當成全部使用者的保證。回查的入口:MDN 的 scroll() 與 view() 文件頁(Baseline 標記與相容表會更新)、GitHub 上 MDN 維護的 browser-compat-data 專案(版本記錄的源頭)。這組功能的官方文件都值得直接讀,規格欄位的名稱跟文件完全對得上。
把「先寫清楚再交辦」的思路搬到別的場景一樣有用:前端介面要 AI 不要做出模板味,可以參考我們整理過的 unslop 審查流程;生圖需求怎麼寫成可驗收的提示,看 GPT Image 的提示詞指南筆記;桌機端想自動捲動頁面的工具則可以看 FlowScroll。手上下一個捲動效果,就從四欄規格表開始寫,寫完跑一輪三個動作的驗收,產出對不對,十秒內看得出來。