跳至主要內容
Skip to content

用 HTML 寫影片:HyperFrames 實戰筆記

📝 本文以「我替部落格做一支開場動畫」為主線,邊做邊記——從安裝、合成結構、GSAP 動畫到實戰踩坑與一個關鍵取捨。


一、前言:我想幫部落格做一支開場

事情的起點很單純:我想替「日沐 SunBathe」這個部落格做一段品牌開場 / 動態 hero

找方案時遇到 HeyGen 開源的 HyperFrames,主打「用 HTML 做影片」——身為前端,這個切入點很對胃口,於是決定實際從零跑一輪,做一支「日沐」太陽升起的開場動畫。

過程比想像中有趣:它的設計哲學(用 HTML 當影片原始碼)、只有實作才會踩到的坑(字體裁切、無限循環壞掉確定性渲染),到最後我才想通的一個關鍵問題——我要的到底是「影片」還是「網頁動畫」? 這篇就把整段經驗記下來。

接下來會帶你看:

  1. HyperFrames 是什麼、為什麼用 HTML 做影片
  2. 怎麼安裝、環境要準備什麼
  3. 一支**合成(composition)**怎麼組成、核心觀念是什麼
  4. 實際動手做 SunBathe 開場
  5. 踩到的坑(本篇重點)
  6. 最後一個關鍵取捨:影片 vs 網頁動畫

全文以「邊做邊記」的第一人稱口吻,程式碼都來自真實做出來的那支合成。

二、HyperFrames 是什麼

一句話:HyperFrames 是 HeyGen 開源的框架,把 HTML / CSS / 媒體 / 可 seek 的動畫,渲染成「確定性」的 MP4 影片。

HTML 就是影片的原始碼

傳統做影片,你的「原始碼」是 .aep(After Effects)、.prproj(Premiere)這類專有二進位格式——只能用特定軟體開、難以版本控制、不可能用程式 diff。

HyperFrames 的核心理念是 HTML is the source of truth:一支影片就是一份 HTML——

  • CSS 決定外觀
  • data-* 屬性描述時間軸(哪個元素何時出現、多久)
  • GSAP timeline 描述動畫
  • 框架負責把這些「seek」成一格一格的畫面,輸出 MP4

於是影片變成純文字、可 git diff、可被程式生成的東西。

為誰設計:AI agent、CLI、自動化

正因為輸入是 HTML、流程走 CLI,它特別適合:

  • AI coding agent——用自然語言叫 agent 寫 / 改影片(這也是為什麼它用「skill」的形式發佈)。
  • CLI / CI 自動化——init → lint → preview → render 全在命令列,可批次、可進 pipeline。
  • hosted workflow——大量、程式化地產生影片(HeyGen 本業就是 AI 影片)。

關鍵詞:「確定性」(deterministic)

這是貫穿全篇的核心,先記著一句:同一個時間點,每次 render 都必須得到一模一樣的畫面。 它約束了你能寫什麼動畫、不能用什麼程式碼——詳細的機制和限制,第四章會拆給你看。

跟 Remotion 差在哪?

HyperFramesRemotion
描述方式原生 HTML + data-*React 元件(JSX)
心智負擔會寫網頁就能上手需熟悉 React
動畫GSAP timeline(seek)React 狀態 + frame 函式
定位偏 agent / CLI 自動產片偏前端工程師寫程式化影片

簡單說:兩者都是「用程式碼做影片」,但 HyperFrames 賭的是「HTML 比 JSX 更通用、更適合給 AI 寫」。

三、安裝與環境

HyperFrames 不是用 npm install 裝的——它本質是一組教 coding agent 的 skill,用 skills CLI 安裝:

bash
npx skills add heygen-com/hyperframes

接著會跑一個互動精靈,問你幾件事。我把實際的選擇與理由記下來。

3-1 要裝哪些 skill(共 15 個)

清單分三類,不用全選

  • 核心 4 個(建議必選)hyperframeshyperframes-clihyperframes-mediahyperframes-registry
  • 動畫轉接器(挑你會用的)gsap(官方主推)、css-animationswaapianimejsthreetypegpulottietailwind
  • 特定用途(用到再加)contribute-catalog(貢獻元件)、remotion-to-hyperframes(從 Remotion 遷移)、website-to-hyperframes(網站轉影片)

💡 全裝也不吃空間——這些是文字說明檔,不是把 GSAP/Three.js 函式庫下載下來。真正的成本是「agent 的 skill 清單變雜」,不是硬碟。

3-2 裝給哪個 agent、裝去哪、怎麼裝

精靈接著問三題,這是我的選擇:

問題我選為什麼
裝給哪個 agentClaude Code.claude/skills要裝給你實際在用的那個 agent 才生效
ScopeGlobal~/.claude/skillsHyperFrames 是通用工具、跟我的部落格無關,不想汙染 repo 的 git
MethodSymlink單一來源、skills update 一次更新全部、不佔額外空間

反過來說:如果你是想「這個專案專用、而且要讓協作者 clone 就有」,Scope 才選 Project(會一起進版控)。

3-3 環境需求

  • Node ≥ 22
  • FFmpeg——但有個重要區別:

⚠️ FFmpeg 只有「render 成 MP4」才需要,preview 預覽完全用不到。 所以你想先玩、先預覽,沒裝 FFmpeg 也能開始。要輸出影片時再補:

bash
brew install ffmpeg   # macOS

裝完 skill、Node 版本對了,就可以進到下一章——產生第一支合成。

四、一支合成(composition)的解剖

在動手寫動畫前,得先看懂「一支合成長什麼樣」。別手刻檔案,用 init 產生骨架:

bash
npx hyperframes init sunbathe-intro --example blank

它會建出這樣的結構(不是只有一個 HTML,還有給 agent 看的說明與設定):

text
sunbathe-intro/
├── index.html        # 主合成(root timeline)— 你主要編輯這個
├── hyperframes.json  # registry 與路徑設定
├── meta.json         # 專案 metadata(id、name)
├── CLAUDE.md         # 給 coding agent 的規則
└── package.json      # dev / check / render 指令

打開 index.htmlblank 範本的核心其實只有三個重點,我們逐一拆解。

4-1 根節點:data-composition-id 宣告一支合成

html
<div
  id="root"
  data-composition-id="main"
  data-start="0"
  data-duration="8"
  data-width="1920"
  data-height="1080"
>
  <!-- clips 放這裡 -->
</div>
  • data-composition-id="main":這支合成的識別名,等下要和 timeline 對應
  • data-duration:整支長度(秒)。所有動畫都活在這個區間內——也是為什麼第六章那個 repeat: -1 不行。
  • data-width / data-height:畫布尺寸(影片解析度)。

4-2 clip:class="clip" + 時間軸屬性 控制「何時出現」

每個會在時間軸上出現的元素都是一個 clip,需要兩樣東西:

html
<div class="clip" data-start="0" data-duration="8" data-track-index="0">...內容...</div>
  • class="clip" 是必須的——框架靠這個 class 判斷「這元素受時間軸控制」,據此決定它在某個時間點該不該顯示。漏掉它,元素就不受控。
  • data-start / data-duration:這個 clip 何時進場、持續多久。
  • data-track-index:軌道編號(像影片剪輯的圖層),不同軌道可疊放、避免重疊衝突。

💡 把它想成影片剪輯軟體:root 是時間軸,每個 clip 是軌道上的一段素材。

4-3 動畫:window.__timelines 上一條 paused 的 GSAP timeline

clip 解決了「何時出現」,但「出現後怎麼」是 GSAP 的事。關鍵是:timeline 必須 paused(暫停),並掛到 window.__timelines,用 data-composition-id 當 key:

js
window.__timelines = window.__timelines || {}

const tl = gsap.timeline({ paused: true }) // ← 一定要 paused
tl.from('.title span', { y: 100, opacity: 0, stagger: 0.16 }, 0.5)
// ...更多 tween...

window.__timelines['main'] = tl // ← key 對應 data-composition-id

為什麼是 paused?因為你不是要「播放」它——

4-4 心智模型:不是「播放」,是「seek」

這是整個框架最該先建立的觀念:

  • 一般網頁動畫是播放:跟著真實時鐘往前跑(CSS animation、requestAnimationFrame)。
  • HyperFrames 是seek:渲染引擎把這條暫停的 timeline,逐格 seek 到精確時間點(例如第 3.5 秒),截一張圖,再 seek 到下一格⋯⋯拼成 MP4。
text
render 流程:seek(0.000s) → 截圖 → seek(0.033s) → 截圖 → seek(0.066s) → 截圖 → …

理解這點,後面所有「坑」都會變得理所當然:

  • timeline 要 paused → 不然它會自己跑,引擎沒辦法控制要 seek 到哪。
  • 不能 repeat: -1 → 無限循環沒有「第 N 格的確定狀態」可算。
  • 不能 Math.random() / Date.now() → 每次 seek 同一時間點都必須得到完全一樣的畫面。

一句話:你不是在「拍動畫」,而是在描述一條「任何時間點都算得出畫面」的函式。確定性,就是從這裡來的。

五、動手做「日沐 SunBathe」開場

觀念到位了,來實際做一支 8 秒開場:暖白背景上,一顆太陽升起並呼吸般脈動,光芒緩緩旋轉,「日沐」兩字逐字浮現。重點是一個方法論——Layout before Animation

5-1 先擺位,再動畫(Layout before Animation)

官方 skill 一直強調的原則:先用靜態 HTML/CSS 把「最完整可見」的那一影格排好,再加 GSAP 動畫

  • 如果你一開始就把元素擺在動畫起點(offscreen、scale: 0opacity: 0),等於是在「猜」最終版面,重疊與跑版要等 render 出來才看得到。
  • 先把結束狀態排好,版面問題在加動畫前就能用肉眼修掉。

所以 HTML 就是一個置中的卡片,元素照最終樣子排:

html
<div
  id="hero-card"
  class="clip scene-content"
  data-start="0"
  data-duration="8"
  data-track-index="0"
>
  <div class="rays" id="rays"></div>
  <div class="sun-wrap"><div class="sun"></div></div>

  <h1 class="title"><span></span><span></span></h1>
  <div class="divider"></div>
  <div class="brand">SunBathe</div>
  <div class="tagline">一個喜歡行光合作用的全端工程師</div>
</div>

5-2 用品牌色排好靜態版面(CSS)

容器用 flex 置中、暖白 radial 背景;太陽是一顆 radial-gradient 圓加外發光;文字層墊在太陽之上(z-index):

css
.scene-content {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 30px;
  padding: 120px;
  background: radial-gradient(circle at 50% 40%, #fffdf5 0%, #f7f7f5 46%, #eef0e8 100%);
  font-family: 'Noto Sans TC', sans-serif;
}

.sun {
  width: 540px;
  height: 540px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 45%, #fff8e6, #f2e9c4 30%, #cdd9af 60%, #9fb682 100%);
  box-shadow:
    0 0 120px 40px rgba(157, 182, 130, 0.45),
    0 0 260px 90px rgba(242, 233, 196, 0.35);
}

.title {
  display: flex;
  gap: 0.06em;
  font-family: 'Noto Serif TC', serif;
  font-weight: 700;
  font-size: 250px;
  color: #3f4a31;
}

🎨 這版用的是 matcha 綠系太陽(#9fb682)。實際放上部落格後我覺得「太綠、不像太陽」,後來把太陽漸層換成暖色金黃——這留到第八章的網頁版再說。

12 道光芒不寫死在 HTML,用固定迴圈動態產生(注意:不能用亂數,否則破壞確定性):

js
const rays = document.getElementById('rays')
for (let i = 0; i < 12; i++) {
  const r = document.createElement('div')
  r.className = 'ray'
  r.style.transform = `rotate(${i * 30}deg)` // 每道間隔 30°
  rays.appendChild(r)
}

5-3 加上動畫(一條 paused timeline)

靜態版面就緒,才開始堆 GSAP。全部掛在同一條 paused timeline 上,用第三個參數安排出場時間點:

js
window.__timelines = window.__timelines || {}
const tl = gsap.timeline({ paused: true })

// 太陽升起(fromTo 保留置中位移,見第六章坑 2)
tl.fromTo(
  '.sun-wrap',
  { xPercent: -50, yPercent: -50, y: 130, opacity: 0 },
  { xPercent: -50, yPercent: -50, y: 0, opacity: 1, duration: 1.3, ease: 'power3.out' },
  0
)
// 太陽呼吸般脈動(有限次數,見第六章坑 1)
tl.to('.sun', { scale: 1.05, duration: 2.4, ease: 'sine.inOut', repeat: 2, yoyo: true }, 0.6)

// 光芒淡入 + 緩慢旋轉
tl.from('.rays', { opacity: 0, scale: 0.6, duration: 1.6, ease: 'power2.out' }, 0.2)
tl.to('.rays', { rotation: 360, duration: 40, ease: 'none' }, 0)

// 日沐 逐字浮現 → 分隔線展開 → 品牌名 → 標語
tl.from(
  '.title span',
  { y: 100, opacity: 0, duration: 0.9, stagger: 0.16, ease: 'power3.out' },
  0.5
)
tl.from('.divider', { scaleX: 0, opacity: 0, duration: 0.7, ease: 'power2.out' }, 1.35)
tl.from('.brand', { y: 40, opacity: 0, duration: 0.8, ease: 'power2.out' }, 1.65)
tl.from('.tagline', { y: 30, opacity: 0, duration: 0.8, ease: 'power2.out' }, 1.95)

window.__timelines['main'] = tl

幾個對應前面觀念的細節:

  • gsap.from vs gsap.to:進場用 from(從 offscreen/透明 → 排好的位置),呼應 Layout before Animation——CSS 的位置才是「真相」,tween 只描述「怎麼到達」。
  • 第三個參數是時間點00.51.35⋯⋯精確排程每個元素何時進場。
  • stagger: 0.16:讓「日」「沐」兩字依序浮現,而不是同時。

寫到這裡,先別急著看效果——下一步是跑 lint,那正是第六章那一串坑的由來。

六、實戰踩到的坑(本篇重點)

寫完第一版合成,先別急著 preview,第一件事是跑 lint

bash
npx hyperframes lint

我那一版的成績是「1 個 error、4 個 warning」。這一節把每個坑拆成「lint 訊息 → 原因 → 修法」三段——這些幾乎都是「動畫在瀏覽器看起來好好的,render 出來卻壞掉」的那種坑,靠肉眼很難發現,但 lint 一抓一個準。

坑 1:gsap_infinite_repeat(這是會擋下 render 的 error)

lint 訊息:

text
✗ gsap_infinite_repeat: GSAP tween uses `repeat: -1` (infinite).
  Infinite repeats break the deterministic capture engine which seeks
  to exact frame times.

原因:HyperFrames 不是「播放」動畫,而是把 timeline seek 到每一個精確的影格時間再截圖。repeat: -1(無限循環)沒有明確的結束時間,引擎無法計算「第 N 影格此刻的狀態」,確定性就破了。

修法:改成有限次數,用總長除以單次週期算出來。我原本太陽脈動寫無限:

js
// ❌ 之前:無限脈動
tl.to('.sun', { scale: 1.05, duration: 2.4, repeat: -1, yoyo: true }, 0.6)

// ✅ 之後:有限次數(3 段 × 2.4s ≈ 7.2s,落在 8s 合成內)
tl.to('.sun', { scale: 1.05, duration: 2.4, repeat: 2, yoyo: true }, 0.6)

光芒旋轉也一樣,原本 repeat: -1,改成「單一長 tween」就好——只要它的長度涵蓋整段合成,根本不需要 repeat:

js
// ✅ 一條 40s 的旋轉 tween,已涵蓋 8s 合成
tl.to('.rays', { rotation: 360, duration: 40, ease: 'none' }, 0)

📌 通則:repeat: Math.floor(總長 / 單次週期) - 1,用 Math.floor(不是 ceil)確保動畫不超出合成長度。

坑 2:gsap_css_transform_conflict

lint 訊息:

text
⚠ gsap_css_transform_conflict: ".sun-wrap" has CSS `transform: translate(-50%, -50%)`
  and a GSAP tween animates y. GSAP will overwrite the full CSS transform,
  discarding any translateX(-50%) centering.

原因:我用經典的 transform: translate(-50%, -50%) 把太陽置中,又用 GSAP 動它的 y 做升起。問題是 GSAP 動 y 時會覆蓋整個 transform,那個 -50% 的水平置中就被吃掉,太陽會跑位。

修法:把置中也交給 GSAP,用 fromTo() 搭配 xPercent / yPercent(lint 明說 fromTo 不受此規則限制):

css
/* CSS 只留定位,移除 transform */
.sun-wrap {
  position: absolute;
  top: 50%;
  left: 50%;
}
js
// 置中(xPercent/yPercent)與升起(y)一起交給 GSAP
tl.fromTo(
  '.sun-wrap',
  { xPercent: -50, yPercent: -50, y: 130, opacity: 0 },
  { xPercent: -50, yPercent: -50, y: 0, opacity: 1, duration: 1.3, ease: 'power3.out' },
  0
)

坑 3:字體會在 render 時 fallback(google_fonts_import / font_family_without_font_face

lint 訊息:

text
⚠ google_fonts_import: Composition loads fonts from fonts.googleapis.com.
  External font requests fail in sandboxed/offline renders.
⚠ font_family_without_font_face: Font families used without @font-face: noto sans tc, noto serif tc.

原因:我直接 <link> 了 Google Fonts。preview 在瀏覽器跑沒問題(抓得到網路字體),但 render 是在 sandbox / 離線環境,抓不到外部字體,「日沐」就會 fallback 成系統字,影片裡的字型跑掉。

修法:把字體換成本地 @font-face.woff2,不要依賴外部網路:

css
@font-face {
  font-family: 'Noto Serif TC';
  src: url('./assets/fonts/NotoSerifTC.woff2') format('woff2');
}

這也是「preview 過 ≠ render 過」最典型的例子——preview 沒抓到的問題,lint 先幫你抓到了。

坑 4:確定性限制——禁用一切「不可重現」的邏輯

這不是單一 lint 規則,而是貫穿整個框架的鐵則:同樣的 timeline 時間點,每次 render 都必須得到一模一樣的畫面。所以以下通通不能用:

  • Math.random():要隨機感,就預先算好固定值寫死,或用 index 推導。
  • Date.now() / new Date():時間相關一律不可。
  • 網路 fetch():外部資料會讓結果不可重現。

我那支開場要產生 12 道光芒,就用固定迴圈而不是亂數:

js
// ✅ 固定迴圈,每次結果都一樣
for (let i = 0; i < 12; i++) {
  const ray = document.createElement('div')
  ray.className = 'ray'
  ray.style.transform = `rotate(${i * 30}deg)`
  rays.appendChild(ray)
}

修完的成績

把 error 修掉、warning 評估過後,再跑一次 lint:

text
◇  0 error(s), 2 warning(s)

剩下的 2 個 warning 是字體那組——因為我這支只要 preview、暫時不 render MP4,就先留著(preview 完全不受影響)。error 一定要清零,warning 看用途決定

七、預覽 vs 渲染

合成寫好、lint 過了,最後就是「看成果」。這裡有兩個很容易搞混的動作——預覽渲染完全不同。

7-1 完整 dev loop

官方建議的順序是:

bash
npx hyperframes init my-video    # 1. 產生骨架
# 2. 寫 / 改 HTML 合成
npx hyperframes lint             # 3. 檢查(必跑,見第六章)
npx hyperframes inspect          # 4. 視覺檢查:文字有沒有溢出框、跑出畫布
npx hyperframes preview          # 5. 瀏覽器即時預覽
npx hyperframes render           # 6. 輸出 MP4

lint 抓的是「程式碼層級」的問題(無限 repeat、transform 衝突);inspect 是用 headless Chrome 把時間軸 seek 一遍,抓「視覺層級」的問題(文字爆框、元素溢出)。兩個互補。

7-2 preview vs render 的關鍵差別

previewrender
指令npx hyperframes previewnpx hyperframes render
需要 FFmpeg
產物瀏覽器即時畫面.mp4
何時用開發中反覆看效果最終輸出影片
  • preview 是長駐伺服器:背景跑著、改檔即時熱重載,開發時讓它一直開著就好。
  • render 才需要 FFmpeg:所以你想先玩、先看效果,根本不必裝 FFmpeg。

7-3 preview 要用 Studio 專案 URL

一個實測小坑:preview 起來後,別直接開 index.html 的檔案路徑,要用 Studio 專案網址——它才有時間軸 scrub、播放控制:

text
http://localhost:<port>/#project/<project-name>

例如我在 sunbathe-intro 用預設 port 跑,網址就是:

text
http://localhost:3002/#project/sunbathe-intro

八、一個關鍵取捨:你要的是影片,還是網頁動畫?

做到這裡我才意識到一個關鍵問題:我到底要的是「一個影片檔」,還是「網頁上會動的東西」? 這兩者的答案,決定你要不要繼續走 render 這條路。

8-1 HyperFrames 的產物是 MP4,不是網頁成品

  • HyperFrames 的終點是 MP4——那份 HTML 合成檔是「原始碼」,不是設計來直接塞進網站的。
  • 裡面那些 data-*class="clip"window.__timelines、固定 1920×1080、paused timeline,全是給渲染引擎 seek 用的機械
  • 如果只是想讓部落格首頁有個「活的」hero,把這份合成 HTML 原封 iframe 進去,會得到一個固定尺寸、暫停不動的東西——完全不對。

8-2 要網頁動畫,就「移植」而不是「轉檔」

正解是:把 CSS 和那條 GSAP timeline 抽出來,做成一個正常的、會自動播放的 Vue 元件。反而比用 HyperFrames runtime 更簡單、更輕、任意解析度都銳利。

我把 SunBathe 的太陽動畫移植成了 HeroSunVisual.vue,過程做了三個針對「網頁」的關鍵調整:

ts
// HeroSunVisual.vue(節錄)
import { ref, onMounted, onBeforeUnmount } from 'vue'

const root = ref<HTMLElement | null>(null)
let ctx: { revert: () => void } | null = null

onMounted(async () => {
  // ① 無障礙:尊重「減少動態效果」,直接停在最終狀態
  const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
  if (reduce || !root.value) return

  // SSR 安全:gsap 只在 client 端動態載入
  const { gsap } = await import('gsap')
  ctx = gsap.context(() => {
    const tl = gsap.timeline()
    tl.from(sunWrap.value, { yPercent: 28, opacity: 0, duration: 1.3, ease: 'power3.out' }, 0)
    // ② 無限循環:網頁沒有確定性限制,repeat:-1 隨便用
    gsap.to(sun.value, { scale: 1.05, duration: 2.4, repeat: -1, yoyo: true })
    gsap.to(rays.value, { rotation: 360, duration: 60, ease: 'none', repeat: -1 })
  }, root.value)
})

onBeforeUnmount(() => ctx?.revert()) // 元件卸載時清掉動畫
css
/* ③ 響應式:用容器單位 cqmin,取代寫死的 px */
.sun-visual {
  container-type: size;
}
.sun {
  width: 34cqmin;
  height: 34cqmin;
}

對照「render 版」與「網頁版」的差異——很多 HyperFrames 的限制,在網頁上反而是優勢:

面向HyperFrames render 版移植的網頁元件版
尺寸固定 1920×1080 px響應式 cqmin,跟著容器縮放
循環禁止 repeat: -1可以無限循環
播放paused + 引擎 seek自動播放(真實時鐘)
確定性random / Date.now無此限制
無障礙不適用可加 prefers-reduced-motion
SSR不適用需把 gsap 留在 client(onMounted

8-3 所以——你該轉檔嗎?

你要的需要轉檔(MP4)做法
網頁上的活動畫(hero)移植成自動播放 Vue 元件
一個影片檔(社群分享、對外發)裝 FFmpeg → render

一句話:要「影片檔」就 render;要「網頁會動」就移植它的動畫。 我的部落格 hero 屬於後者,所以最後根本沒用到 FFmpeg。

九、小結:什麼時候值得用 HyperFrames

跑完整輪,我對「該不該用 HyperFrames」有了比較具體的判斷。

它真正發光的場景——當你需要的是「程式化、可重現、可自動產的影片檔」:

  • 社群短片、產品 demo、開場 / 片頭
  • 字幕 + 配音(TTS)影片
  • 用 AI agent 或 CI 批次產生大量影片
  • 想把影片「原始碼」納入 git 版控、能 diff、能 code review

它不適合的場景——當你要的其實是「網頁上會動的東西」:

  • 部落格的互動 / 裝飾動畫、捲動特效
  • 這種情況直接寫 GSAP / WAAPI 元件更輕、更銳利、還能響應式(就像我最後對首頁 hero 做的)

最大的收穫

對我來說,這趟最值得的不是「學會一個工具」,而是被逼著想清楚一個常被混淆的問題:

要「影片檔」就用 HyperFrames render;要「網頁會動」就把它的動畫移植成元件。

搞清楚這條分界,比任何 API 都實用。而 HyperFrames 用 HTML 當影片原始碼的這個賭注——讓「做影片」變成一件前端寫程式、AI 能接手的事——確實是個很有意思的方向。


延伸資源: