動畫網頁
概述
網頁動畫的失敗有兩種形式:它不執行,或是執行得很糟糕。兩者都有特定的、可追查的原因。猜測緩動曲線是最後才該做的事,而不是第一步。
核心原則:在調整動畫之前,先證明它在你認為的元素上執行。
關於固定位置與滾動觸發場景的設計,請參閱 choreographing-scroll-stories。此技能專注於機械層面:讓動畫執行,並且執行流暢。
當動畫完全不執行時
請依序檢查。前三項幾乎涵蓋了所有情況。
| 症狀 | 原因 | 檢查方式 |
|---|---|---|
| 沒有任何東西移動,元素可見 | 選擇器未匹配到任何元素 | 在控制台執行 document.querySelectorAll(sel).length |
| 沒有任何東西移動,元素不可見 | 揭露效果設為 opacity: 0,但觸發器從未觸發 | 檢查元素的計算樣式 opacity |
| 淡入淡出效果從未發生 | 兩個圖層都處於 opacity: 1;只有「啟動」規則存在 | 必須有一個設定進入圖層為 0 的靜止規則 |
| 載入時立即觸發 | 觸發器在版面配置穩定前就已評估 | 應在 load 事件後執行,而非 DOMContentLoaded,以便圖片能決定頁面尺寸 |
| 觸發一次後就不再觸發 | once: true 加上元素又重新進入 | 這是否是預期行為? |
| 在隱藏分頁或無頭面板中不執行 | 當 document.hidden 時,時間軸被暫停 | 強制執行:gsap.ticker.lagSmoothing(0); gsap.ticker.tick() |
| 觸發點偏差數百像素 | 圖片載入後才測量觸發位置 | 在載入和調整大小時執行 ScrollTrigger.refresh()(加上 200 毫秒防抖) |
淡入淡出這個問題值得詳細說明,因為它在程式碼審查中看起來是正確的:
/* 單獨這段什麼都不做。 */
.tile.is-swapped .layer.is-alt { opacity: 1 }
/* 靜止狀態才是讓它成為動畫的關鍵。 */
.layer.is-alt { opacity: 0 }
.tile.is-swapped .layer.is-alt { opacity: 1 }
首頁進場與環境動畫
- 標題要能快速可見。 將進場動畫剪裁到標題約在 1.1 秒內顯示。一個 3.8 秒的進場動畫就像一個載入畫面。
- 照片透過從一個單元格淡出、進入另一個單元格來切換。它們永不滑動。 每張照片都是一個元素,絕不重複出現在畫面上,也不會在它的副本淡入時同時淡出。
- 環境變化要緩慢且不規則。 每個磁貼每 1.8 到 3.4 秒變化一次,讓網格看起來像活著,而任何單一磁貼卻變化緩慢。不要為了讓每個磁貼變化得更快而縮短間隔。
- 以輪次方式隨機排列。 每個磁貼在重複之前都會先變化一次,並且每次選擇都來自最近三次中較遠的那一半。
- 暫停那些沒有人看到的東西。 環境循環會在螢幕外和隱藏分頁中停止,並在原來的地方恢復。
- 進場動畫不會在每次返回時重播。 在進場動畫穩定後,除了環境層之外,沒有任何東西會移動。
捲動顯示
- 只在載入時實際位於摺疊線下方的元素上設定
opacity: 0。全面隱藏所有元素意味著任何在摺疊線之上的元素都是不可見的,直到可能永遠不會發生的滾動出現。 - 在
top 88%觸發顯示,而不是完全交集。等待元素完全進入視野,代表動畫會在讀者已經看到它之後才執行。 - 在同級元素之間交錯延遲,而非在元件內部。卡片之間 60-90ms,卡片內部為 0。
- 每個群組使用一個觀察器進行批次處理,而非每個元素一個。
影片
自動播放的背景影片有一個普遍的問題:影片在螢幕外播放,一個觀察器暫停了它,等到讀者到達時,循環已經過了 15 秒,開頭的節奏就遺失了。
完全移除 autoplay 屬性。
在初始化時執行 pause() 和 load()。
只在第一次進入畫面時:currentTime = 0,然後執行 play()。
在 992px 以下或裝置像素比低於 1.5 時,提供 1x 檔案,並在影片解碼前提供一個海報幀。首頁的高流量影片也是頻寬消耗的大戶:請進行審計。
卡頓
靜態照片上的緩慢漂移是最常見的原因。Ken Burns 效果中,每幀移動低於約 1 像素時,會導致每一幀都重新取樣圖片,看起來像是閃爍而非移動。
- 要嘛移動的幅度足夠成為動作,要嘛就完全不要移動。
- 對於渲染影片中的靜止畫面,請逐位元組複製編碼後的幀,而不是重新編碼一個完全相同的幀。任何其他做法都會導致漂移。
- 檢查循環接縫。如果第 0 幀與最後一幀不同,循環會每週期明顯跳動。測量它們之間的平均絕對差值。
- 不要雙重平滑。一個平滑滾動函式庫加上一個沉重的
scrub值,會導致頁面跟不上滾輪。當平滑滾動函式庫存在時,請使用scrub: 0.2。
黏性元素
CSS Grid 內部的 position: sticky 是相對於網格區域來解析的,而不是整個容器。一個只跟自身內容一樣高的黏性元素沒有移動空間,並且會立即釋放。
.grid { align-items: stretch } /* 區域現在是全高的 */
.col { position: static; height: 100% }
.inner { position: sticky; top: 10rem }
降級方案
每個動畫都有三個降級方案,必須寫在動畫規格中並進行測試:
- 減少動畫。
prefers-reduced-motion: reduce和一個?static查詢參數,會渲染出堆疊好的、最終的狀態:沒有固定位置、沒有隱藏元素、每個場景都在結束狀態。也要涵蓋你添加的所有 CSStransition。列出動畫選擇器但忘記容器上的 transition,會導致顏色仍在滑動。 - 無腳本。 所有東西保持可見。如果腳本從未啟動,約 3.5 秒後會添加一個 class,CSS 會顯示它底下的所有內容。
- 手機版。 固定位置場景會堆疊起來。必須明確說明並進行測試。
驗證
不要僅從程式碼就報告動畫可以運作。請在頁面中確認:
- 每個動畫群組的選擇器計數不為零
- 在捲動之前,摺疊線上方元素的計算
opacity為 1 - 影片
currentTime在它首次變為可見的瞬間低於 0.5 秒 - 減少動畫模擬能讓頁面完全可讀且靜止
- 無腳本情況下所有東西都保持可見
- 沒有動畫作用於
width、height、top或left;使用transform和opacity - 環境循環在螢幕外時不會進行任何樣式變更
紅旗警示
- 在確認選擇器匹配之前就調整持續時間或緩動曲線
- 一個
.is-active規則沒有對應的靜止狀態規則 transition: all- 因為「感覺區塊很空」而添加的動畫
- 會滑動的照片
- 同一張照片在畫面上出現兩次