VidTSX 2D 影片產生器
根據鏡頭或影片的描述,生成可用於 VidTSX 的生產級 2D TSX 影片檔案。這些檔案是由 Remotion 逐影格渲染的合成元件,因此正確性比巧妙更重要:一個非單調的 interpolate 範圍或一個誤用的 useState 就會導致渲染崩潰。
此技能涵蓋2D 動態圖形。3D 作品(使用 @remotion/three / React Three Fiber)不在此範圍。
如何使用此技能
- 清楚的單一鏡頭 → 直接產生
.tsx檔案。 - 複雜、多鏡頭或視覺上不明確的需求 → 先以一條簡短訊息確認鏡頭分解或視覺方向,然後再產生。不要過度詢問;一行確認就夠了。
- 交付為實際的
.tsx檔案,而非內嵌程式碼。這些 TSX 元件通常超過 20 行,且使用者會直接編輯這些檔案。預設為每個檔案一個鏡頭(原子化,易於編輯),除非使用者要求單一合併的交付物。 - 檔案內容僅包含元件程式碼 — 歡迎加入下方結構性的區塊註解,但檔案內不得有 Markdown 圍欄或散文式說明。
- 先渲染並檢查,再宣告完成 — 請參閱驗證渲染結果。僅靠推理完成的鏡頭不算完成;靜止畫(對於揭示/捲動效果,則需要每個關鍵提示點的影格)是驗收標準。
輸出規格
尺寸預設
| 格式 | 寬度 | 高度 | 使用情境 |
|---|---|---|---|
| 橫向 (horizontal) | 1920 | 1080 | YouTube、簡報 |
| 直向 (vertical) | 1080 | 1920 | TikTok、Reels、Shorts |
| 正方形 (square) | 1080 | 1080 | Instagram 貼文 |
預設值
- 格式: 橫向 (1920×1080)
- 長度: 5 秒
- FPS: 30
- 風格: 極簡(參見
references/style-presets.md)
強制檔案結構
每個產生的檔案都遵循此骨架。標示的區塊註解是刻意保留的,它們能讓大型檔案易於瀏覽。
import React from 'react';
import {
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
AbsoluteFill,
Sequence,
} from 'remotion';
// =============================================================================
// 合成組態
// =============================================================================
export const compositionConfig = {
id: 'ComponentName', // 僅限 PascalCase — 不可有連字號或底線
durationInSeconds: 5,
fps: 30,
width: 1920,
height: 1080,
};
// =============================================================================
// 風格常量
// =============================================================================
const COLORS = {
primary: '#6366f1',
secondary: '#8b5cf6',
accent: '#06b6d4',
background: '#0f0f23',
text: '#ffffff',
} as const;
const TYPOGRAPHY = {
fontFamily: 'Inter, system-ui, sans-serif',
} as const;
const EASINGS = {
easeOut: Easing.bezier(0.33, 1, 0.68, 1),
easeIn: Easing.bezier(0.32, 0, 0.67, 0),
easeInOut: Easing.bezier(0.37, 0, 0.63, 1),
overshoot: Easing.bezier(0.34, 1.56, 0.64, 1),
} as const;
// =============================================================================
// 預先產生的資料(在模組層級運算一次,而非在渲染期間)
// =============================================================================
const seededRandom = (seed: number): number => {
const x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
};
// =============================================================================
// 主要元件
// =============================================================================
const ComponentName: React.FC = () => {
const frame = useCurrentFrame();
const { fps, durationInFrames, width, height } = useVideoConfig();
return (
<AbsoluteFill style={{ backgroundColor: COLORS.background }}>
{/* 內容 */}
</AbsoluteFill>
);
};
export default ComponentName;
將 ComponentName 替換為描述該鏡頭的 PascalCase 名稱,根據選定的風格預設設定 COLORS 區塊,並根據需求設定 width/height/durationInSeconds/fps。
正確性規則
這些不是風格偏好——它們是讓渲染成功的關鍵。VidTSX(透過 Remotion)透過在固定的影格編號下呼叫元件來渲染每個影格。沒有事件迴圈、影格之間沒有持久性、也沒有真實時鐘時間。任何假定這些存在的東西都會導致渲染失敗。
動畫始終基於影格
- 使用
useCurrentFrame()+interpolate()驅動所有動畫。 - 絕對不要使用
useState、useEffect、setTimeout、setInterval或 CSS 動畫/轉場。它們不符合逐影格模型,且會產生錯誤或非確定性的輸出。 - 對於任何隨機或程序性的效果,請使用
seededRandom(或等效的種子函數),以確保同一個影格的每次渲染結果都相同。在模組層級預先產生粒子/隨機陣列,切勿在渲染函數內進行——每次影格重新計算會導致閃爍。 - 錯開動畫,使元素按順序進入,而非一次全部出現。這樣看起來更好,也是預期的外觀。
interpolate — 輸入範圍必須嚴格遞增
interpolate 的輸入範圍必須嚴格單調遞增。重複或遞減的值會在執行時拋出錯誤。
// ✅ 正確
interpolate(frame, [0, 30, 60], [0, 1, 0]);
// ❌ 拋出錯誤 — 輸入範圍遞減
interpolate(frame, [60, 30, 0], [0, 1, 0]);
若要反向對應,請翻轉輸出範圍,絕不要翻轉輸入範圍:
// ✅ 正確 — 將 0→100, 1→0
interpolate(value, [0, 1], [100, 0]);
// ❌ 錯誤
interpolate(value, [1, 0], [100, 0]);
對於基於索引的時機控制,確保 startFrame < endFrame 並始終進行裁切(clamp):
const startFrame = index * 30;
const endFrame = startFrame + 30;
interpolate(frame, [startFrame, endFrame], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
始終傳遞 extrapolateLeft: 'clamp' 和 extrapolateRight: 'clamp',除非確實需要無邊界值——沒有它們,數值會在關鍵影格之前/之後超出範圍。
Easing — 使用 Easing.bezier(),絕不使用包裝器語法
像 Easing.out(Easing.cubic) 這樣的包裝器形式會導致崩潰。在一個地方定義命名的貝茲曲線(請參見骨架中的 EASINGS 區塊)並引用它們:
// ❌ 會崩潰
Easing.out(Easing.cubic);
Easing.in(Easing.quad);
// ✅ 正確
interpolate(frame, [0, 30], [0, 1], {
easing: EASINGS.easeOut,
extrapolateRight: 'clamp',
});
chroma-js — 僅限命名空間匯入
// ✅ 正確
import * as chroma from 'chroma-js';
const color = chroma('#00bfff').brighten(0.5).hex();
// ❌ 錯誤 — "chroma is not a function"
import chroma from 'chroma-js';
@remotion/paths — 僅存在真實的函數
這些輔助函數不存在且絕對不能使用:
makeCircle()、makeRect()、makeTriangle()、makeLine()、makePie()、makePolygon()、makeEllipse()、makeStar()。
只有這些匯入是有效的:
import { evolvePath, getLength, getPointAtLength, getTangentAtLength } from '@remotion/paths';
手動編寫 SVG 路徑字串,並使用 evolvePath 為它們製作動畫:
const circlePath = 'M 50 10 A 40 40 0 1 1 49.99 10 Z';
const rectPath = 'M 0 0 L 100 0 L 100 50 L 0 50 Z';
const linePath = 'M 0 0 L 100 100';
const { strokeDasharray, strokeDashoffset } = evolvePath(progress, rectPath);
合成 ID
compositionConfig.id僅限 PascalCase — 沒有連字號,沒有底線(例如 ProductReveal,而不是 product-reveal 或 product_reveal)。
版面配置
安全區域
- 上方 10%: 預留給平台 UI。
- 下方 15%: 預留給字幕/按鈕。
- 將主要內容保持在垂直方向的 25%–75% 之間。
置中輔助
const centered: React.CSSProperties = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
};
排版
| 元素 | 大小 | 粗細 |
|---|---|---|
| 標題 | 72–120px | 700–900 |
| 副標題 | 36–48px | 500–700 |
| 內文 | 28–36px | 400–500 |
始終對文字元素設定 margin: 0 — 瀏覽器預設值會使版面偏移。
風格預設
在此儲存庫中,預設是品牌風格,而不是某個預設。 鏡頭從 remotion/src/brand.ts 匯入 COLORS、GRADIENT、RADIUS、SHADOW、EASINGS,並從 remotion/src/fonts.ts 匯入 FONT_HEADLINE、FONT_DISPLAY、FONT_BODY、FONT_MONO,而不是使用骨架中的區域 COLORS/TYPOGRAPHY 區塊,並遵循 brand.md。FONT_HEADLINE(Instrument Serif)只有一種粗細:切勿對其設定 fontWeight。除非其 compositionConfig 指定了 ground: 'night',否則鏡頭應位於日間底色上;套件元件(lib/shorts.tsx、BrandBg)會從中選取顏色,因此請從 brand.ts 讀取 useGround() 來設定您自行設定的任何顏色。
當使用者指定某種風格時,請從 references/style-presets.md 載入其調色盤和特性,並將 COLORS 物件放入骨架中。六種預設為:極簡 (minimalist)、孟菲斯 (memphis)、新野獸派 (neo-brutalism)、玻璃擬態 (glassmorphism)、霓虹/賽博龐克 (neon/cyberpunk) 以及企業 (corporate)。如果未指定風格,則使用上述的品牌風格。
驗證渲染結果(請勿跳過)
一個鏡頭在您親眼看過之前不算完成。撰寫完 .tsx 後,至少渲染一張靜止畫並打開它——絕不要交出一個您僅憑推理得出的鏡頭。渲染成功且數字看起來正確,但文字溢出其容器、圖片裁切錯誤、元素超出畫面,或兩個東西重疊,這些都是常見的問題。只有螢幕截圖才能捕捉到這些。
- 渲染一張靜止畫(例如
node scripts/render-all.mjs --still --scale=1 <ShotId>)並檢查 PNG 檔案。 - 對於時間驅動的揭示效果(元素在某個提示點進入、捲動效果、分階段),單張處於 60% 長度時間點的靜止畫是不夠的——請渲染完整片段並在每個關鍵時刻提取影格(使用
ffmpeg -ss <t> -i out/<id>.mp4 -frames:v 1 f.jpg),或在多個偏移點渲染靜止畫。驗證每個提示點的狀態,而不只是一個影格。 - 特別檢查:圖片是否符合其位置(重要內容未被裁切;對於異質的真實圖片,偏向於在匹配的背景上使用
objectFit: contain)、文字未被裁剪或溢出、無任何元素超出安全區域、以及每個動畫元素在應該出現時確實可見。 - 如果它被合成到其他影片素材上,也應從最終的烘焙輸出中抽樣檢查一個影格,而不僅僅是單獨的鏡頭。
修復螢幕截圖所揭示的問題,然後重新渲染並再次檢查。將「我已渲染且看起來正確」——並顯示該影格——視為完成的標準。
最終輸出
產生完整的 .tsx 檔案,且檔案內部不得包含任何其他內容——沒有前後的 Markdown、沒有程式碼前後的評論或說明。在對話中交接檔案時,附上一句簡短的單行備註是可以接受的。