Motion Frames — 基於幀數的動態設計系統
無需影片軟體即可產出專業級動態設計的完整方法:整部動畫為一個 HTML 頁面,其場景是幀數的純函式,由無頭 Chromium 逐幀截圖,再透過 ffmpeg 組合成 MP4。一切皆為確定性:相同輸入產生相同像素——可平行渲染、從任意幀數恢復、並實現 1/60 秒的聲音與畫面同步。
1. 不可違反的規則:幀數的純函式
- 整個場景存在於
renderFrame(n)中,其中n為幀數(60fps)。 - 禁止使用時鐘(
Date.now、performance.now、透過requestAnimationFrame累積狀態)以及禁止無種子的隨機性:僅能透過索引/幀數的確定性hash(i)實現隨機。 - 運動 = 從事件開始以來的經過時間的封閉公式:
- 解析彈簧(欠阻尼):
spring(t, freq, damp)含 overshoot——這是所有動作的標準物理模型(進場、彈跳、堆疊)。 - 具名緩動函式:
easeOutCubic、easeInOutCubic、easeOutBack。 - 區段:
seg(n, a, b)= 從幀數範圍 a 到 b 的 0→1 進度;clamp、lerp用於內插任何數值(位置、顏色、變形幾何)。
- 解析彈簧(欠阻尼):
- 標準格式:1080x1920,60fps。時長由內容決定(快速測試:8 秒 = 480 幀)。
2. 管道(此技能的 pipeline/ 資料夾)
| 檔案 | 說明 |
|---|---|
pipeline/template.html | 基礎頁面,內含輔助函式(T、seg、spring、緩動函式、hash)以及 renderFrame(n) 骨架 |
pipeline/render.mjs | 使用 CDP 的無依賴渲染器:node render.mjs <page.html> <dir> <num_frames> 1080 1920 [start_frame] |
pipeline/preview.mjs | 僅渲染選定幀數為 PNG:node preview.mjs <page.html> <dir> "0,60,160,300" <total> 1080 1920 |
pipeline/sfx_kit.py | 合成音效套件(numpy/scipy),與幀數綁定 |
- Chromium:渲染器會依序尋找
CHROME_BIN環境變數、腳本旁的chrome-headless-shell,以及工作空間的已知路徑。並非所有 Chromium 都適用:具有域/WebGL 限制政策的建置(例如 Hatch 管理的 Chromium)會拒絕獨立執行——請使用獨立的 chrome-headless-shell。 - 速度:
FMT=jpeg(品質 90)比 PNG 快約 2 倍,且結果無差異。僅預覽時使用 PNG。 - 分片:長影片可分成 2 個平行程序渲染(區間的一半,使用
start_frame參數),每個程序使用各自的PORT(CDP)。在約 8GB 的機器上最多 2 個平行——3 個會耗盡 RAM。 - 組合:
ffmpeg -framerate 60 -i dir/f%04d.jpg -i audio.wav -c:v libx264 -pix_fmt yuv420p -crf 18 -c:a aac -b:a 192k -movflags +faststart out.mp4。 - 本地字型與圖片:使用相對於 HTML 的路徑(可透過
file://運作)。品牌字型:下載.woff2並產生本地@font-faceCSS——不要在渲染時依賴網路。場景中使用的圖片:在頁面 body 中加入隱藏的<img>預載,否則前幾幀會缺少圖片。
3. 生產流程(依此順序)
- 腳本與參考:定義每個場景的句子。逐幀研究參考(使用 ffmpeg 提取 contact sheet)並記錄語法——不要複製表面。
- 先處理音訊(如有旁白):使用
ffmpeg -af silencedetect檢測靜音後再決定剪輯;使用 faster-whisper(small,word_timestamps=True,將音訊作為從 16k WAV 讀取的 numpy 陣列傳入——faster-whisper 的內部解碼在某些 PyAV 版本中會出錯)進行轉錄,並將關鍵字的時間轉換為幀數地標(second × 60)。切勿憑空捏造語速。 - 品牌調查(產品影片):獲取真實品牌識別——下載網站的 HTML/CSS,提取 logo(圖片檔案)、色板(CSS 中最常見的十六進位色碼)和字型。在影片中使用官方 logo 和字型。
- 按場景設計:每個場景一個函式(
s1(n)、s2(n)...),邊界位於語音地標處。每個場景在誕生時即與其主要事件同步。 - 強制預覽:使用
preview.mjs建立 15-30 幀的 contact sheet 以檢查節奏與構圖——並務必以實際大小檢查 2-3 個關鍵幀:contact sheet 會誤導(隱藏文字碰撞、寬度溢出和小細節)。 - 微事件音效(第 8 節),使用與動畫相同的幀數。
- 分片渲染 + 組合 + 品質檢查:從最終 MP4(而非單獨的幀)提取幀數進行檢查,然後再交付。
4. 運動語言(區分專業與通用的關鍵)
- 電影級開場:第一秒為標題——使用彈簧進場的大型字體排版與攝影機運動,背景具有深度。絕無靜態畫面。
- 攝影機永不停歇:所有鏡頭中緩慢的縮放漂移(1→1.03–1.09);利用運動(各層之間具有視差的 dolly/zoom/pan)掩蓋剪輯。
- 密度:每 0.5–1.5 秒一個微事件。如果某段超過 2 秒沒有任何事發生,那就是錯誤的。
- 萬物皆有物理:具有 overshoot 的彈簧、墜落與彈跳中的 squash & stretch、主要運動前的 anticipation。預設禁止線性運動。
- 轉換是事件,不是剪輯:物體 morph(藥丸變成條狀,條狀變成卡片)、帶模糊的 zoom-through,或 zoom-punch(縮放穿越 1→1.09,在場景邊界處約 18 幀的 crossfade)。
- 精簡色板:一種色調的背景 + 一種強調色並帶有發光效果;粗體字型;大量留白。
5. 背景:絕不單調,始終生動
禁止使用純黑色(或任何純色)背景——背景擁有自己的生命、流動以及背景/中景/前景之間的視差。三種經過驗證的配方:
- 生動工作室(深色宣傳片):使用強調色調的漸層,2 個緩慢漂移的模糊色塊,頂部的聚光燈,確定性雜湊的漂浮散景粒子,具有透視點格的地面 + 發光的地平線 + 中央元素下方的反射。
- 液態極光(連續場景):3-4 條拉長的徑向漸層帶,
mix-blend-mode: screen,強烈模糊,以利薩如曲線漂移並緩慢旋轉;當命令改變場景「顏色」時,兩個色板進行 crossfade;頂部有粒子灰塵。 - 品牌冰面(淺色動態文字):品牌色的淺色漸層,薄荷色絲綢流動(與液態極光相同技術,淺色版),細微的點格與灰塵——乾淨但充滿生機。
6. 真正的 3D(全力以赴——這是影片落後於參考的最常見原因)
- CSS
perspective+rotateX/rotateY應用於手機、卡片和儀表板;元素漂浮,具有對傾斜做出反應的動態陰影。 - 真實視差:在所有的 zoom/pan 中,背景、中景和前景以不同速度移動。
- CSS 中的 3D 立方體和盒子:每個面都是一個 div,使用
translateZ = 寬度/高度的一半定位(錯誤計算會導致邊緣出現縫隙)。 - 上升的等距樓梯/區塊、具有深度的堆疊、玻璃(當背景簡單時使用
backdrop-filter)以及掃過的鏡面高光。 - 實用規則:如果場景可以描述為「文字 + 平面矩形」,則缺少 3D。添加傾斜、圖層或深度,直到它看起來像是「拍攝」的,而不是「繪製」的。
7. 四種語法(逐幀結構)
A. 電影/Morph(開場與風格測試)
電影標題 → 具有 squash & stretch 的物體沿虛線軌跡運動 → 攝影機俯衝,物體進行 morph(圓形→藥丸→畫面) → 旋轉的 3D 元素,鏡頭拉近至表面 → 動態文字,帶有手繪圓形 → 帶有彈簧圖表的玻璃面板 → 結尾回到起點(如果需要,完美循環)。
B. 高階 App 宣傳(參考 Opal/Qwix 風格)
- 生動的環境背景 + 聚光燈;一種強調色並帶有發光/輝光。
- 開場:UI/手機以 3D 透視漂浮,dolly-in 並有拉焦(開始模糊,然後對焦)。
- 攝影機潛入一個細節(圖表條柱),該細節成為下一個鏡頭。
- 玻璃藥丸,帶有圖示 + 數字計數(easeOut)+ 標籤;邊框帶有移動的光弧。
- 聚合:支撐線從藥丸繪製出來,匯聚到一個巨大的帶有 ▲ 的數字。
- App 圖示 morph + 粒子爆炸。
- App 畫面 + 巨大的 CTA 藥丸按鈕,帶有光掃描。
- Logo 揭示:SVG dash 繪製符號 + wordmark + 小網址。
C. 連續互動場景(參考「Built with Claude」)
單一場景,無剪輯,於液態極光之上:符號繪製 → 命令藥丸進入 → 游標移動並點擊藥丸 → 藥丸 morph 為提示欄 → 提示逐字輸入(游標閃爍,最後一個字突出顯示) → 提示欄 morph 為結果(內部自行構建的卡片:環、條、播放、進度 %) → 輸入第二個命令,改變整個背景顏色 → 結尾帶有符號 + 簽名。故事是「有人在使用產品」。
D. 輕型動態文字(葡萄牙語排版廣告)
品牌冰面背景;品牌字型的粗體排版為主體,隨著語音節奏逐字顯示,關鍵字使用強調色,彩色幽靈陰影和手繪塗鴉(劃線、圓圈、swoosh——SVG dash)。穿插講述故事的實際 UI:使用物理堆疊的通知、比較條、圍繞 logo 的節點軌道系統、對話氣泡聊天、帶有圖表的狀態卡片、巨大的計數器。場景邊界使用 zoom-punch;中間有暗色反轉節拍(「而且還不止於此!」);三拍口號搭配圖示;結尾為 logo + 游標點擊的 CTA + 網址。
8. 聲音:微事件音效
- 音軌在
sfx_kit.py中組合:每個聲音事件使用與視覺事件相同的幀數。 - 詞彙:
pop(點擊、卡片、大字)、tick(打字、條柱、計數器)、whoosh(轉換和 morph)、shimmer(確認、揭示)、pad(背景和聲床)。 - 密度與影片相同:如果有視覺微事件,就有聲音。在有旁白的部分,將音軌混音至約 0.5,讓語音主導。
- 切勿生成或取代使用者的聲音:旁白始終是使用者的,保持原樣(僅在分析音訊並在剪輯後重新建立同步後才進行靜音剪輯)。
9. 反通用檢查清單(交付前確認)
- 字體排版是主角,還是僅僅是 UI 標籤?(必須是主角)
- 是否在所有場景中每 0.5–1.5 秒都有微事件?
- 是否有任何東西以物理方式堆疊、落下或碰撞——還是所有東西都孤立漂浮?
- 是否存在互動(游標點擊、輸入提示、正在使用的產品)?
- 是否有手繪塗鴉層(圓圈、底線、箭頭、劃線)?
- 至少有一個轉換是物體 morph,而不僅僅是縮放?
- 背景是否具有自己的生命流動以及層間的視差?
- UI 是否包含講述故事的「真實」內容(數值、對話、名稱),而不是抽象佔位符?
- 是否有軌道系統、路徑文字或其他「簽名」元素?
- 3D 是否通過第 6 節的測試?
- 每個微事件都有音效嗎?
10. 商業事實(不要捏造數字)
- 變成商業承諾的數字(費率%、通過率%、價格)不可捏造:如果品牌未公佈,則使用不帶數字的視覺比較(高條柱 × 低條柱)、作為動畫手法的數量/成長計數器,以及定性語言(「高」、「低很多」)。
- 關於產品的不確定事實應在腳本撰寫前標明,絕不要在渲染後。
11. 已發生的錯誤(請勿重複)
- Contact sheet 會誤導:縮圖隱藏文字碰撞和寬度溢出。務必以實際大小驗證關鍵幀。
- SVG 中的重複屬性:同一元素上有兩個
stroke-dasharray——第一個勝出,draw-on 動畫無聲失效。每個元素一個屬性。 - 硬編碼的場景索引:返回固定場景(
return 5)的函式在場景數量改變時會破壞最後一個場景。應從邊界陣列推導。 - 3D 立方體有縫隙:
translateZ錯誤(≠ 維度的一半)的面會在邊緣留下縫隙。 - 3 個平行分片導致記憶體不足(在小機器上)。最多 2 個。
- 系統的 Chromium 可能不適用(域/WebGL 限制):請使用獨立的 chrome-headless-shell。
- 無預載的圖片僅在最初幾幀後才出現:在 body 中加入隱藏的
<img>可解決。 - 「幾乎正確」的場景:以大字型測量文字寬度——118px 的標語可能超過 1080px 並嚴重斷行;在大型預覽中檢查實際寬度。
12. 範例專案(此系統的真實作品)
~/workspace/motion-frames/teste_8s.mp4— 語法 A:球/morph/立方體/動態文字循環,8 秒。~/workspace/iphone18/iphone18_pro.mp4— 逐字同步旁白(53 秒),CSS 3D iPhone。~/workspace/kalo/kalo_app_v2.mp4— 語法 B,搭配生動工作室背景 + AI 餐點掃描場景,15 秒。~/workspace/kalo/feito_com_muse.mp4— 語法 C:連續場景、游標、輸入提示、morph、帶 crossfade 的極光,14 秒。~/workspace/turtle/turtlepags.mp4— 完整語法 D,搭配旁白、真實品牌識別(來自網站的 logo/色板/字型),53 秒。