Remotion LabRemotion Lab
影片凍結影片的特定片段

凍結影片的特定片段

使用 Freeze 元件在 Remotion 序列中暫停並恢復特定時間區間,實現影片凍結效果。

凍結影片的特定片段

在製作影片時,有時需要在特定時間點暫停(凍結)畫面,並在稍後從凍結點恢復播放。Remotion 提供了 <Freeze> 元件來實現這個效果。

核心概念

<Freeze> 元件會讓其子元件停在某個指定的影格(frame)。搭配 active 屬性,可以讓凍結效果只在特定時段生效。<Sequence>from 屬性則用來在凍結結束後,從正確的位置繼續播放,避免時間軸錯位。

完整程式碼範例

以下片段示範了如何在序列中設定兩個凍結區間:第一個從影格 0 開始凍結 25 格,第二個從影格 30 開始凍結 50 格。凍結結束後,動畫從暫停點繼續。

import React, { useMemo } from 'react';
import { Freeze, Sequence, useCurrentFrame } from 'remotion';
 
// 一個簡單的計數器元件,顯示當前影格數
export const Counter: React.FC = () => {
  return (
    <div className="flex h-full justify-center items-center text-6xl">
      {useCurrentFrame()}
    </div>
  );
};
 
// 定義凍結設定:每個凍結區間的起始影格與持續長度
const FREEZES = [
  {
    frame: 0,           // 在動畫影格 0 時凍結
    durationInFrames: 25, // 凍結持續 25 格
  },
  {
    frame: 30,          // 在動畫影格 30 時凍結
    durationInFrames: 50, // 凍結持續 50 格
  },
];
 
// 計算每個凍結區間在時間軸上的實際位置
const getFreezes = () => {
  let summedUpFreezes = 0;
  const freezeFrames = [];
  for (const freeze of FREEZES) {
    freezeFrames.push({
      start: summedUpFreezes + freeze.frame, // 在時間軸上的起始位置
      durationInFrames: freeze.durationInFrames,
      from: summedUpFreezes,                 // Sequence 的偏移量
      frame: freeze.frame,                   // 要凍結的影格
    });
    summedUpFreezes += freeze.durationInFrames;
  }
  return freezeFrames;
};
 
export const FreezePortion: React.FC = () => {
  const freezes = useMemo(() => {
    return getFreezes();
  }, []);
 
  const frame = useCurrentFrame();
 
  // 找出下一個即將發生的凍結區間
  const nextFreeze = freezes.find(
    (freeze) => frame < freeze.start + freeze.durationInFrames,
  );
 
  // 找出當前正在生效的凍結區間
  const activeFreeze = freezes.find(
    (freeze) =>
      frame >= freeze.start && frame < freeze.start + freeze.durationInFrames,
  );
 
  // 計算 <Sequence> 的 from 偏移量,確保凍結後從正確位置繼續播放
  const from = useMemo(() => {
    if (activeFreeze) {
      // 正在凍結中:不需要偏移
      return 0;
    }
 
    if (nextFreeze) {
      // 尚未到達下一個凍結點:使用累積的凍結偏移量
      return nextFreeze.from;
    }
 
    // 所有凍結都已結束:套用全部凍結的累積偏移
    return (
      freezes[freezes.length - 1].from +
      freezes[freezes.length - 1].durationInFrames
    );
  }, [activeFreeze, freezes, nextFreeze]);
 
  return (
    <Freeze
      frame={activeFreeze ? activeFreeze.frame : 0}
      active={Boolean(activeFreeze)}
    >
      <Sequence layout="none" from={from}>
        <Counter />
      </Sequence>
    </Freeze>
  );
};

運作原理說明

getFreezes() 函式

這個函式會將 FREEZES 陣列轉換成帶有時間軸位置資訊的物件陣列:

屬性說明
start凍結區間在整體時間軸上的開始位置(含前面所有凍結的累積時長)
durationInFrames此次凍結持續幾格
from傳給 <Sequence>from 屬性,用來補償已過的凍結時長
frame凍結時要停在哪個影格

時間軸示意

假設有如上兩個凍結設定,時間軸展開如下:

整體時間軸:  0  ─── 24  25 ─── 29  30 ─── 79  80 ─── ...
              [  凍結0   ]  [播放] [  凍結1          ] [播放]
動畫實際進度:  停在 f0        f0─f29  停在 f30              f30─...
  • 影格 0–24:畫面凍結在動畫影格 0
  • 影格 25–29:動畫正常播放影格 0–4(因為 from=0,Sequence 從頭開始)
  • 影格 30–79:畫面凍結在動畫影格 30
  • 影格 80 起:動畫繼續從影格 30 播放(from 補償了所有凍結時長)

<Freeze>active 屬性

active 設為 false 時,<Freeze> 不會產生任何效果,子元件正常渲染。這讓我們可以一直掛載同一個 <Freeze> 元件,並用 active 來切換凍結狀態。

<Sequence>from 屬性

from 是負數偏移的關鍵:它告訴 <Sequence> 從哪個全域影格開始算起為第 0 格。當凍結結束後,若不調整 from,動畫會從頭重播而不是從暫停點繼續,因此必須傳入已積累的凍結時長作為補償。

自訂多個凍結區間

您可以自由調整 FREEZES 陣列來設定任意數量的凍結:

const FREEZES = [
  { frame: 10, durationInFrames: 30 },  // 在影格 10 凍結 30 格(1 秒@30fps)
  { frame: 60, durationInFrames: 15 },  // 在影格 60 凍結 15 格(0.5 秒@30fps)
  { frame: 90, durationInFrames: 60 },  // 在影格 90 凍結 60 格(2 秒@30fps)
];

搭配 <Video> 使用

同樣的技術可以套用在 <Video><OffthreadVideo> 元件上:

import { Freeze, Sequence, OffthreadVideo } from 'remotion';
 
export const FrozenVideo: React.FC = () => {
  // ... 同上計算 activeFreeze, from ...
 
  return (
    <Freeze frame={activeFreeze ? activeFreeze.frame : 0} active={Boolean(activeFreeze)}>
      <Sequence layout="none" from={from}>
        <OffthreadVideo src="https://example.com/video.mp4" />
      </Sequence>
    </Freeze>
  );
};

相關 API