產品展示影片 是做什麼的?
將靜態的產品截圖或設計稿轉變為精緻的動畫展示 — 無需螢幕錄製、沒有抖動的真實游標、也沒有 UI 版本不一致的問題。為每個畫面加上框架,讓合成游標滑順地移動到精確的像素位置,點擊時產生波紋效果,然後縮放到重點區域並加上功能說明。由於輸入是靜態且動作由程式碼驅動,每一個數值都是確定的,當截圖改變時,可以完全一致地重新渲染。
使用時機
- 應用程式 / SaaS 展示、功能發佈影片、引導式入門教學(15–90 秒)。
- 展示僅有截圖或設計稿的 UI 點擊流程。
- 強調資料密集儀表板中的某個焦點區域(縮放至區域 + 註釋標註)。
為什麼截圖優於螢幕錄製
| 螢幕錄製 | 截圖驅動(此技能) |
|---|---|
| 真實游標會漂移、過衝、抖動 | 游標沿著緩動路徑移動到精確目標 |
| 要修正一個步驟就必須重新錄製整段 | 替換一張 PNG,重新執行相同程式碼即可 |
| UI 在錄製中途變更,滑鼠會亂找 | 每個畫面都是乾淨的最終狀態素材 |
| 難以在不產生像素化的情況下縮放 | 以 2 倍(DPR 2)匯出截圖 → 清晰的縮放 |
請以顯示尺寸的兩倍(retina / deviceScaleFactor: 2)捕獲輸入,以便 2 倍縮放時仍保持清晰。請參閱 references/screenshot-demo.md 以取得 Playwright 捕獲腳本。
展示循環
每個展示都遵循相同的節奏,針對每個功能重複進行。停留足夠長的時間以便閱讀,有目的地移動,精準點擊,然後呈現結果。
| 節拍 | 時間預算 | 任務 |
|---|---|---|
| 畫面靜止 | 0.5–1 秒 | 讓觀看者在任何動作開始前先定位 |
| 游標移動到目標 | 0.4–0.8 秒 | 緩動移動(ease-in-out),非線性 |
| 點擊波紋 + 狀態變化 | 0.2–0.3 秒 | 觸覺回饋;畫面在同一幀做出反應 |
| 縮放至焦點區域 | 進入 1–2 秒,停留 2–4 秒 | 2 倍是最佳選擇 — 細節足夠,保留上下文 |
| 功能說明文字 | 與縮放同時停留 | 一個效益短語,例如「一鍵匯出」 |
移動鏡頭或游標,但極少同時移動兩者 — 同時進行兩個動作會分散注意力。
為截圖加上框架
原始的截圖看起來像錯誤回報;加上框架後看起來才是產品。將每個畫面包裹在瀏覽器或裝置的框架內,並放在有品牌的背景上。
// 瀏覽器框架 — 放入截圖,取得可信的視窗
const BrowserFrame = ({ url, children }) => (
<div style={{ borderRadius: 12, overflow: "hidden", background: "#1e1e22",
boxShadow: "0 40px 80px rgba(0,0,0,.35)" }}>
<div style={{ height: 40, display: "flex", alignItems: "center", gap: 8, padding: "0 14px" }}>
{["#ff5f57", "#febc2e", "#28c840"].map((c) => (
<span key={c} style={{ width: 12, height: 12, borderRadius: "50%", background: c }} />
))}
<div style={{ marginLeft: 12, flex: 1, height: 24, borderRadius: 6, background: "#2b2b30",
color: "#9aa", fontSize: 13, display: "flex", alignItems: "center", padding: "0 10px" }}>
{url}
</div>
</div>
<div style={{ background: "#fff" }}>{children}</div>
</div>
);
在網址列中放入真實 URL(增加真實感),加上柔和陰影和漸層背景。對於行動裝置,改用帶有瀏海和圓角的電話框架。將框架樣式保留在一個主題物件中,以便每個畫面都保持一致。
動畫游標 + 點擊波紋(核心動作)
從當前幀驅動位置,而不是依賴 CSS 過渡或時鐘 — 影片渲染器需要每一幀都是幀編號的純函數,否則游標會不同步並閃爍。
import { useCurrentFrame, interpolate, spring, useVideoConfig } from "remotion";
// 在 startFrame 和 startFrame+dur 之間,從 `from` 到 `to` 進行緩動移動
const useCursor = (from, to, startFrame, dur) => {
const frame = useCurrentFrame();
const t = interpolate(frame, [startFrame, startFrame + dur], [0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: (x) => x * x * (3 - 2 * x) }); // smoothstep
return { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };
};
export const Cursor = ({ from, to, startFrame, dur, clickAt }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const { x, y } = useCursor(from, to, startFrame, dur);
// 波紋只展開一次,在點擊幀時觸發
const since = frame - clickAt;
const showRipple = since >= 0 && since < 18;
const r = spring({ frame: since, fps, config: { damping: 18 }, durationInFrames: 18 });
return (
<>
{showRipple && (
<div style={{ position: "absolute", left: x, top: y, transform: "translate(-50%,-50%)",
width: 12 + r * 60, height: 12 + r * 60, borderRadius: "50%",
border: "2px solid rgba(80,140,255,.9)", opacity: 1 - r }} />
)}
<svg width="28" height="28" viewBox="0 0 28 28"
style={{ position: "absolute", left: x, top: y, filter: "drop-shadow(0 2px 3px rgba(0,0,0,.4))" }}>
<path d="M3 2 L3 21 L9 15 L13 24 L16 23 L12 14 L20 14 Z" fill="#fff" stroke="#222" strokeWidth="1.4" />
</svg>
</>
);
};
游標使用 smoothstep,因此它會從靜止中加速,並在抵達目標時減速(線性游標看起來很機械化)。在相同的 clickAt 幀上觸發波紋和畫面的狀態變化,使因果關係一致。請參閱 references/screenshot-demo.md 以取得完整的合成程式碼,其中包含跨越多個畫面的多次點擊。
縮放至區域(UI 的 Ken Burns 效果)
若要縮放到某個焦點元素,請縮放畫面並將 transform-origin 設定為該元素的中心,這樣縮放就會以該元素為中心收斂,而不是以畫面中心為中心。
const ZoomToRegion = ({ target, scale = 2, startFrame, dur, children }) => {
const frame = useCurrentFrame();
const s = interpolate(frame, [startFrame, startFrame + dur], [1, scale],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: (x) => 1 - Math.pow(1 - x, 3) }); // easeOutCubic
return (
<div style={{ transform: `scale(${s})`, transformOrigin: `${target.x}px ${target.y}px`,
willChange: "transform" }}>
{children}
</div>
);
};
將縮放倍率限制在 2 倍左右(超過 3 倍會失去上下文並造成混亂)。保持縮放狀態 2–4 秒 — 足夠長的時間來閱讀細節 — 然後在移動到下一個畫面之前,再以緩動方式縮回。務必將縮放與註釋標註配對使用,不要單獨使用。
註釋標註與功能說明文字
為重點功能加上標註,然後讓它消失。註釋是一個標籤加上一個連接器(箭頭、圓圈或底線),指向焦點元素。
- 高亮元素:使用柔和的光環或調暗的周圍環境(
box-shadow: 0 0 0 9999px rgba(0,0,0,.45)可以切出一個聚光燈孔)。 - 說明文字:每個節拍只寫一個效益 — 簡短的片語(「即時分析」),而不是一句話。在縮放時出現,在下一個步驟之前消失。
- 將說明文字保持在下方三分之一處,並在標題安全區域內,以便在裁切為 9:16 格式時仍然可見。
請參閱 references/annotation-and-transitions.md 以了解聚光燈遮罩、箭頭連接器以及帶有輸入追蹤的說明文字軌道。
畫面間的過場效果
在步驟之間,使用一致的方式移動畫面。選擇一種並堅持使用:
| 過場效果 | 感覺 | 使用時機 |
|---|---|---|
| 推入 / 滑入 | 「下一步」的前進動作 | 線性引導式流程 |
| 交叉縮放 | 有動量,類似應用程式 | 跳入詳細檢視畫面 |
| 交叉淡出 | 平靜,中性 | 切換上下文(設定 → 儀表板) |
| 共用元素的匹配剪輯 | 無縫 | 相同卡片在跨畫面時持續存在 |
在點擊動畫解析之前,保留目前的畫面,然後再進行過場 — 絕不要在動作進行中剪輯。請參閱 references/annotation-and-transitions.md。
輸出檢查清單
- 輸入以 2 倍(DPR 2)捕獲;2 倍縮放時保持清晰。
- 每個游標/縮放數值都是
useCurrentFrame()的純函數 — 動畫狀態不使用 CSS 過渡。 - 游標沿著緩動路徑移動(smoothstep);波紋和狀態變化在同一個點擊幀上觸發。
- 每個畫面都加上框架(瀏覽器/裝置),並帶有真實 URL 和一致的主題。
- 一次只移動一個動作(鏡頭或游標);縮放 ≤ 2 倍,停留 2–4 秒。
- 每個節拍有一個效益說明文字,位於標題安全的下方三分之一處;過場效果使用一致的方式。
交付與驗證(渲染靜止幀 → MP4)
打包的輔助工具(
scripts/):使用scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png將您的靜止幀拼接成聯繫表,然後使用scripts/probe-mp4.sh out.mp4 [WxH] [fps]驗證編碼。請參閱scripts/README.md。
這是一個較重的任務:交付物是 MP4,而不是一個即時頁面。示範會攝取使用者的素材 — 截圖/設計稿、可選的標誌 — 因此驗證主要重點是正確的圖片是否確實載入並落在畫面中。
輸出合約:
- 一個 Remotion 專案,已註冊合成(
<Composition>+ zodschema+defaultProps);每個游標/縮放/過場數值都是useCurrentFrame()的純函數(不使用 CSS 過渡、計時器、Date.now())。 - 透過
staticFile()載入截圖,並使用delayRender/continueRender進行門控,以便在幀渲染之前確保 2 倍 PNG(以及任何網路字型)已存在 — 否則畫面會在示範中間空白彈出。 - 交付物 = 渲染後的
out/*.mp4加上專案(當截圖更改時重新渲染)。
驗證循環 — 渲染靜止幀 → 檢查 → 編碼。 先渲染便宜的 PNG,只有在它們正確後才渲染影片。
# 使用您將正式使用的 props(真實的截圖路徑)進行幀精確的靜止幀渲染,而不是 defaultProps
npx remotion still ProductDemo out/f-settle.png --frame=15 --props=demo.json # 畫面加框,移動前
npx remotion still ProductDemo out/f-click.png --frame=90 --props=demo.json # 游標在目標上 + 波紋
npx remotion still ProductDemo out/f-zoom.png --frame=150 --props=demo.json # 縮放至焦點區域 + 說明文字
# 結束幀 = durationInFrames - 1(npx remotion compositions 會讀取它)
檢查每個 PNG 的正確性(正確的截圖已載入;游標在精確像素上;點擊波紋和狀態變化在同一幀;說明文字正確)以及瑕疵(圖片空白/未載入、畫面超出畫布、縮放後像素化或超出框架、說明文字超出標題安全的下方三分之一區域、長寬比錯誤/出現黑邊)。
# 僅在靜止幀乾淨後才執行:
npx remotion render ProductDemo out/demo.mp4 --props=demo.json
npx remotion render ProductDemo out/demo.gif --props=demo.json --codec=gif # README 的首個畫面證明
批次處理(一個範本,多個產品): 當針對每個產品重新換皮示範時,在批次渲染整個目錄之前,先透過靜止幀驗證一個代表性產品的 props — 一次性發現空白圖片或畫面超出畫布的錯誤,而不是 N 次。
在您完成之前:
- 靜止幀在靜止/點擊/結束時渲染乾淨 — 沒有錯誤,截圖確實已載入(非空白)。
- 游標落在精確目標上;波紋 + 狀態變化在同一幀;說明文字正確且在標題安全的下方三分之一處。
- 所有動畫均由幀驅動 — 沒有 CSS 過渡 / 計時器 /
Date.now()/Math.random()。 - 正式交付的 props(真實截圖路徑)能正確渲染,而不僅僅是
defaultProps。 - 完整的 MP4 已編碼並可播放;(可選)已為 README 渲染 GIF。
參考檔案
references/screenshot-demo.md— 一個完整可執行的 Remotion 示範:使用 Playwright 以 2 倍解析度捕獲截圖、瀏覽器/手機框架元件、一個多步驟合成(可讓游標跨越多個畫面並帶有點擊波紋)以及一個渲染命令。references/annotation-and-transitions.md— 聚光燈遮罩、箭頭/圓圈註釋連接器、一個帶有輸入功能說明的文字軌道,以及四種畫面間過場效果,附帶幀驅動的實作和多長寬比(16:9 / 9:16)的安全區域說明。