我開發出了一套『背景逐格動畫』系統,而且還免費開源啦!
林政賢 ·
你現在在 tangyi.mx 首頁往下捲的時候,背景會跟著動。停下來,畫面就停在那一格;往回捲,它就倒著走。那不是影片。
我們把首頁那個「捲動控制背景」開源了
那是一串影格,由程式碼逐格驅動。為了做出這件事,我們寫了 scroll-frame-sequence:一套 535 行、零相依(zero-dependency)的 JavaScript 函式庫。它不依賴任何外部套件,直接把影片拆成序列影格再由捲動位置精準控制,完全繞過瀏覽器對影片自動播放與捲動同步的種種限制。
開發它的理由很明確:我們在做複雜的影音互動頁時,無法忍受原生影片在頻繁捲動下的卡頓感。網頁一旦掉幀,使用者體驗立刻崩壞。這套系統把素材處理工具封裝成四支 Python 指令,能直接產出適用各種解析度的拼接圖。如果你正在做高互動的網頁,可以先看看我們的 AI 能力展示,看這種架構怎麼在手機端維持流暢——這不只是為了效能,也是為了讓互動動畫在不同裝置上有一致的視覺品質。
捲動動畫怎麼避免手機記憶體崩潰?
你以為圖片只是圖片,但對瀏覽器來說,每一張解碼後的圖像都在跟記憶體搶空間。這是我們開發過程中踩過最大的坑:手機的渲染極限非常殘酷,一旦超過門檻,頁面直接閃退或卡死。
瀏覽器解碼一張圖片佔用的記憶體,是一個固定公式:寬 × 高 × 4 bytes。也就是說,就算拼接圖(spritesheet)原始檔案很小,解碼後的體積依然可能瞬間塞爆裝置。
實測:拼接圖解析度 vs 解碼後記憶體
所以我們把拼接圖鎖在特定規格內,禁止單一拼接圖用過高的解析度。之所以不厭其煩地算這些記憶體負載,是因為在手機端做互動網頁,一旦因為圖片太大導致頁面重載,流失率會直接拉高。如果你的頁面捲到一半直接白屏,先檢查拼接圖的原始尺寸——別讓一張圖吃掉手機一半的 RAM,這不是瀏覽器的問題,是我們開發者的責任。
載入太慢?三層載入與二分逼近法
解決記憶體崩潰還不夠。如果使用者捲動時還得等漫長的載入條,留存率依然慘不忍睹。我們設計了一套「三層載入」,把資源分批交付:
- 底層(約 2MB):拼接圖當骨架,優先載入,確保網頁一開啟動畫就能回應捲動。
- 中層(約 5MB):隨後載入全解析度影格,逐步覆蓋底層的模糊預覽。
- 上層:針對停留區塊做循環處理,使用者捲到那裡時才動態載入該片段的循環素材。
為了不讓使用者乾等,下載順序用「二分逼近法」:不是從第一格排到最後一格,而是跳著載——0 → 149 → 75 → 37 → 112,依此類推。好處是只要下載到約兩成的檔案量,使用者就能完整瀏覽一遍全片的視覺動線;中間幾格還沒到,也不會中斷節奏。
這不只是讓頁面「看起來比較快」,是在高延遲的行動網路下維持互動感。你在 AI 能力展示 看到的捲動效果,底層就是這套分層策略——避免一次請求大流量檔案,降低伺服器負載,也降低使用者的等待焦慮。
用 Python 自動化抓影格
為了確保影格品質與流暢度,我們寫了四支 Python 工具取代手動截圖。第一支是影格提取 extract_frames.py:若影片全長 577 格,建議取 150 格樣本,系統會用 round(i × 576 / 149) 精準分配,比單純設 fps 濾鏡更能保住首尾定格的準確性。
加上 --weighted 參數,畫面變化大的橋段會分到更多影格,靜態場景自動減少,省流量。完整指令:
python tools/extract_frames.py 來源.mp4 frames/ --count 150 --scale 1920:1080 --quality 72
取得影格後,用 build_spritesheet.py 把碎圖合併成拼接圖,設定 9 欄(--cols 9)讓瀏覽器渲染時能高效處理圖像記憶體:
python tools/build_spritesheet.py frames/ spritesheet.webp --cols 9
對品質沒把握的話,先用 contact_sheet.py 做一張預覽圖,用 --mark 標記 1、75、150 這些關鍵幀,確認抽樣後的畫面銜接如預期。這幾支腳本省掉我們以前逐幀檢查的時間,也確保所有素材進到產線時格式都已標準化。
AI 素材首尾接不起來?Ping-pong 模式與 JSON 座標表
AI 生成的動態素材有個硬傷:首尾畫面接不平,直接循環會跳一下。我們實測發現,當素材首尾的差異倍數落在 2.3 到 7.7 倍之間,硬循環的效果極差。這時候別執著做首尾相連的素材,直接改用 ping-pong 模式——影格序列「正播後倒播」,物理性避開對接斷點。
我們用 make_loop.py 處理這類需求,循環幀數設 24 幀保持流暢:
python tools/make_loop.py 循環素材.mp4 hold_mid/ --count 24
循環處理完,手機端的顯示又是另一個坑。為了讓直式螢幕精準呈現橫向素材,我們捨棄硬編碼,改用一張 JSON 格式的「座標時間表」:記錄每一幀對應的 frame 編號、在手機上的偏移量 cx(畫面寬度比例)與 fit 模式。前端捲動時據此動態調整背景的偏移與縮放,關鍵影像永遠落在螢幕中央,不會因為螢幕尺寸不同被裁掉。如果你的素材在手機上出現留白,先檢查 JSON 裡的 fit 是否設為 cover,以及 cx 有沒有依畫面中心校正。
兩個最隱晦的坑:sticky 失效與資料庫字串上限
第一個是 position: sticky 無預警失效。當你在 <html> 或 <body> 層級設了 overflow-x: hidden,瀏覽器會強制建立一個新的捲動容器(scrolling container),直接打斷 sticky 的定位鏈。解法很單純:改成 body { overflow-x: clip; },它能切斷溢出而不建立額外的捲動上下文。
第二個在後台。我們想把 150 個影格網址存進資料庫,發現單一文字欄位會觸發長度限制——150 個網址組成的字串大約 20KB,硬塞會被截斷。最後的解法是「分塊儲存」:
- 150 個 URL 切成每 25 個一組,分別存入欄位。
- 前端讀取時重組,結果快取在
localStorage。 - 快取有效期一週,避免頻繁讀資料庫,也降低頁面載入時的請求量。
如果你在後台整合時遇到互相覆蓋的問題,記得在資料結構裡加「序列識別碼」。這是我們做 APP 產品 時為了區分不同素材專案加的關鍵標記,讓多個頁面共用同一個上傳器時,資料不會因為 ID 衝突錯位。
畫質與效能的權衡:WebP 壓縮與權重設定
追求極致瘦身,很容易掉進「畫質換空間」的邊際效應陷阱。我們實測:全面改用 WebP 取代 JPEG,能省下約 53% 的檔案大小,這是最有效的一招。但再往下壓就會失望——品質從 q75 降到 q55,畫面明顯粗糙,實際只再省約 20%。投資報酬率極低,除非頻寬極度受限,建議守在 q72 到 q75,這是視覺與體積的最佳平衡點。
另一個有趣的數據:AI 生成的素材壓縮率優於實拍畫面。AI 畫面的細節通常較平滑,比原生攝影小了約 55%。但調權重(weight)時,千萬別把桌機參數直接套到手機——行動裝置抽樣幀數較少(桌機 150 幀,手機建議 48 幀),權重比例沒跟著調,捲動會出現嚴重的頓挫感。實作上,手機端的權重維持在桌機的三分之一,這不是憑感覺,是為了讓小螢幕快速捲動時,畫面變化與捲動進度的同步率保持一致。
實作指南與開源資源
這套解法整理成 scroll-frame-sequence,535 行、零相依,已正式開源。如果你正面臨行動裝置影片捲動卡頓、或瀏覽器自動播放限制的困擾,可以直接納入你的開發清單。
- 原始碼庫:GitHub 儲存庫,含四支處理素材的 Python 腳本。
- 套件管理:npm 安裝,直接導入前端專案。
動手前,建議先到 實作展示頁面 感受實際的捲動流暢度,那裡完整示範了三層載入與二分逼近法。如果不需要資料庫管理,只要產生 manifest.json、把拼接圖與影格放在靜態空間,就能用這套庫做出複雜的 scrollytelling 互動。遇到 position: sticky 失效或資料庫字串上限這類邊界問題,請參考文件裡的 Bug 排除建議,別在正式環境重踩同一個坑。
關於這篇的數字:記憶體佔用(134MB/538MB)、WebP 壓縮率(53%)、q75→q55 的空間差(約 20%)、AI 素材與實拍的體積差(約 55%)都是本站首頁素材的實測,換素材數字會不同。
作者:林政賢(導演 · Gen AI 創作者兼工程師 · 瑭宜影像創意工作室創辦人)