凍結影片的特定片段
使用 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
<Freeze>— 凍結子元件到指定影格<Sequence>— 控制子元件的時間軸偏移useCurrentFrame()— 取得當前影格數