Remotion LabRemotion Lab
建構應用支援多種幀率

支援多種幀率

學習如何在 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',
});

更好的做法是讓動畫與幀率無關(推薦):

推薦:使用 fps 計算幀數
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>fromdurationInFrames 時也應使用 fps 值:

正確使用 fps 於 Sequence
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()fpsdelaydurationInFrames 時:

在 spring 動畫中使用 fps
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

Root.tsx(動態 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

以下是一個完整範例,讓使用者選擇要使用的幀率進行渲染:

ExportDialog.tsx
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 組件範例

以下是一個完整的多幀率支援組件,確保所有動畫都是幀率無關的:

remotion/MultiRateComposition.tsx
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>
  );
};
remotion/Root.tsx(多 FPS 支援)
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() 方法。

另請參閱