支援多種幀率
學習如何在 Remotion 中支援多種幀率,包括撰寫與幀率無關的動畫以及動態切換 FPS
你可能想要為 composition 支援多種幀率。例如,建構一個選項讓使用者可以以 30 FPS 或 60 FPS 匯出影片。本文件概述了實現此目標的最佳實踐。
撰寫與幀率無關的動畫
在進行時間動畫時,使用 useVideoConfig() 中的 fps 值來計算當前時間。
以下動畫在幀率改變時會改變速度(不推薦):
import { interpolate, useCurrentFrame } from 'remotion';
const frame = useCurrentFrame();
// 從第 1 秒動畫到第 2 秒(假設固定 30 FPS)
// 如果 FPS 改變,動畫速度也會改變!
const animationProgress = interpolate(frame, [30, 60], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});更好的做法是讓動畫與幀率無關(推薦):
import { useVideoConfig, interpolate, useCurrentFrame } from 'remotion';
const frame = useCurrentFrame();
// 從第 1 秒動畫到第 2 秒(與幀率無關)
const { fps } = useVideoConfig();
const animationProgress = interpolate(frame, [1 * fps, 2 * fps], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});在 Sequence 中使用 fps
在指定 <Sequence> 的 from 和 durationInFrames 時也應使用 fps 值:
import React from 'react';
import {
interpolate,
useCurrentFrame,
Sequence,
useVideoConfig,
} from 'remotion';
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
// 顯示 3 秒(與幀率無關)
const element = (
<Sequence durationInFrames={3 * fps}>
<div />
</Sequence>
);在 spring() 中使用 fps
在傳遞 spring() 的 fps、delay 和 durationInFrames 時:
import { spring, useCurrentFrame, useVideoConfig } from 'remotion';
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const animationProgress = spring({
frame,
fps,
durationInFrames: 2 * fps, // 持續 2 秒
delay: 1 * fps, // 延遲 1 秒後開始
});動態改變幀率
以下是如何根據輸入 props 來切換 <Composition> 的 fps:
import { Composition, useCurrentFrame, useVideoConfig } from 'remotion';
// 顯示當前 FPS 的範例組件
const VariableFps: React.FC<{ frameRate: '30fps' | '60fps' }> = () => {
const { fps } = useVideoConfig();
return <div>{fps} FPS</div>;
};
export const Root: React.FC = () => {
return (
<Composition
id="VariableFps"
component={VariableFps}
width={1080}
height={1080}
durationInFrames={100}
calculateMetadata={({ props }) => {
return {
fps: props.frameRate === '60fps' ? 60 : 30,
};
}}
defaultProps={{
frameRate: '30fps',
}}
/>
);
};讓使用者選擇匯出 FPS
以下是一個完整範例,讓使用者選擇要使用的幀率進行渲染:
import React, { useState } from 'react';
type FrameRate = '24fps' | '30fps' | '60fps';
interface ExportOptions {
frameRate: FrameRate;
quality: number;
}
export const ExportDialog: React.FC<{
onExport: (options: ExportOptions) => void;
}> = ({ onExport }) => {
const [frameRate, setFrameRate] = useState<FrameRate>('30fps');
const [quality, setQuality] = useState(80);
const handleExport = () => {
onExport({ frameRate, quality });
};
return (
<div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
<h3>匯出設定</h3>
<div style={{ marginBottom: '12px' }}>
<label>幀率:</label>
<select
value={frameRate}
onChange={(e) => setFrameRate(e.target.value as FrameRate)}
>
<option value="24fps">24 FPS(電影風格)</option>
<option value="30fps">30 FPS(標準)</option>
<option value="60fps">60 FPS(流暢)</option>
</select>
</div>
<div style={{ marginBottom: '12px' }}>
<label>品質:{quality}%</label>
<input
type="range"
min={1}
max={100}
value={quality}
onChange={(e) => setQuality(Number(e.target.value))}
/>
</div>
<button onClick={handleExport}>開始匯出</button>
</div>
);
};完整的多 FPS 組件範例
以下是一個完整的多幀率支援組件,確保所有動畫都是幀率無關的:
import React from 'react';
import {
AbsoluteFill,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from 'remotion';
interface MultiRateProps {
title: string;
frameRate: '24fps' | '30fps' | '60fps';
}
export const MultiRateComposition: React.FC<MultiRateProps> = ({ title }) => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
// 所有動畫都基於秒,而非幀數
const INTRO_DURATION_SEC = 1;
const TITLE_APPEAR_SEC = 0.5;
// 淡入效果(0 到 1 秒)
const opacity = interpolate(
frame,
[0, INTRO_DURATION_SEC * fps],
[0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
// 彈跳效果(0.5 秒後開始)
const scale = spring({
frame,
fps,
delay: TITLE_APPEAR_SEC * fps,
durationInFrames: 0.8 * fps,
config: {
damping: 200,
},
});
// 淡出效果(最後 1 秒)
const fadeOut = interpolate(
frame,
[durationInFrames - fps, durationInFrames],
[1, 0],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
return (
<AbsoluteFill
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
opacity: opacity * fadeOut,
}}
>
<div
style={{
fontFamily: 'sans-serif',
fontSize: '72px',
fontWeight: 'bold',
color: 'white',
transform: `scale(${scale})`,
textShadow: '0 4px 12px rgba(0,0,0,0.3)',
}}
>
{title}
</div>
</AbsoluteFill>
);
};import React from 'react';
import { Composition } from 'remotion';
import { MultiRateComposition } from './MultiRateComposition';
export const RemotionRoot: React.FC = () => {
return (
<>
<Composition
id="MultiRate"
component={MultiRateComposition}
width={1920}
height={1080}
calculateMetadata={({ props }) => {
const fpsMap = {
'24fps': 24,
'30fps': 30,
'60fps': 60,
};
const fps = fpsMap[props.frameRate] ?? 30;
return {
fps,
// 保持總時長不變(3 秒),但幀數會根據 fps 變化
durationInFrames: 3 * fps,
};
}}
defaultProps={{
title: '你好,Remotion!',
frameRate: '30fps' as const,
}}
/>
</>
);
};不建議使用 FPS 轉換器
之前,本頁面展示了一個 FPS 轉換器程式碼片段。不建議使用它,因為它不能與媒體標籤(<Html5Video>、<Audio>、<OffthreadVideo> 等)一起使用。
如果你需要改變幀率,請使用上面描述的 calculateMetadata() 方法。