シーン・キュー・拍
順番に並べる、トランジションでつなぐ、段階的に表示する、拍に同期する。
シーンを並べる、リストを順に表示する、拍に合わせて切り替えるなど、よく使うパターンのヘルパーです。Sequence、Group、フレームに基づくので、スクラブでき、結果も一定です。テキスト効果・カメラ・線は次のページ、シード付き乱数とノイズは @celesta/math で説明します。
シーンを続けて並べる
<Series> は子の <Series.Sequence> を順番に再生するので、開始フレームではなく各シーンの長さを指定できます。offset を負にすると前のシーンと重なります。computeSeries() は描画せず同じ計算を行い、コンポジション全体の長さを求めるのに便利です。
const SCENES = [
{ name: 'intro', durationInFrames: 90, Scene: Intro },
{ name: 'body', durationInFrames: 240, Scene: Body },
{ name: 'outro', durationInFrames: 60, Scene: Outro },
];
const { durationInFrames } = computeSeries(SCENES);
export default function Root() {
return (
<Composition width={1920} height={1080} fps={30} durationInFrames={durationInFrames}>
<Series>
{SCENES.map(({ name, durationInFrames, Scene }) => (
<Series.Sequence key={name} durationInFrames={durationInFrames}>
<Scene />
</Series.Sequence>
))}
</Series>
</Composition>
);
}
シーンをトランジションでつなぐ
<TransitionSeries> は Series と同じくシーンを順に並べ、2 つのシーンの間に <TransitionSeries.Transition> を置けます。'cut' はカット、'crossfade' は次のシーンを前のシーンの上にフェードイン、'slide' は次のシーンが前のシーンを押し出し、'wipe' は動く直線の境界の向こうに次のシーンを見せます。トランジションは前後のシーンを durationInFrames の分だけ重ねるので、computeTransitionSeries() で各シーンの開始位置と、重なりの分だけ短くなった全体の長さを求められます。
import { Audio, Composition, Easings, Rect, useVideoConfig } from '@celesta/react';
import {
TransitionSeries,
computeTransitionSeries,
useTransitionVolume,
} from '@celesta/transitions';
const FADE = { type: 'crossfade', durationInFrames: 15 } as const;
const PUSH = { type: 'slide', durationInFrames: 20, from: 'right', easing: Easings.easeInOutCubic } as const;
// 90 + 120 + 90 frames, less the 15 and 20 the transitions share: 265.
const { durationInFrames } = computeTransitionSeries([
{ durationInFrames: 90 }, FADE, { durationInFrames: 120 }, PUSH, { durationInFrames: 90 },
]);
function Card({ color }: { color: string }) {
const { width, height } = useVideoConfig();
return (
<>
<Rect width={width} height={height} fill={color} />
{/* Fades with the picture. Animated volume exports from the desktop
app or the CLI; the browser playground mixes constant volumes only. */}
<Audio src="./room-tone.wav" volume={useTransitionVolume(0.6)} />
</>
);
}
export default function Root() {
return (
<Composition width={1920} height={1080} fps={30} durationInFrames={durationInFrames}>
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={90}><Card color="#22314F" /></TransitionSeries.Sequence>
<TransitionSeries.Transition {...FADE} />
<TransitionSeries.Sequence durationInFrames={120}><Card color="#2F6F62" /></TransitionSeries.Sequence>
<TransitionSeries.Transition {...PUSH} />
<TransitionSeries.Sequence durationInFrames={90}><Card color="#B5651D" /></TransitionSeries.Sequence>
</TransitionSeries>
</Composition>
);
}
各シーンは通常の Sequence で、入りのトランジションの最初のフレームがフレーム 0 です。後のシーンが手前に描かれます。前後どちらかのシーンより長いトランジションや、入りと出のトランジションを合わせた長さに満たないシーンはエラーになるので、空白や 3 つのシーンの同時表示は起こりません。重なったシーンの音声は同時に再生されます。useTransitionVolume(volume) はシーン内の Audio に映像と一緒にフェードする音量を返し、useTransitionSeriesScene() はほかのタイミング調整のためにシーン前後のトランジションを返します。
順番に入場させる
progress(frame, start, durationInFrames, easing?) は「区間のどこまで進んだか」を 0〜1 に制限した値で返し、多くの入場効果の基礎になります。<Stagger each={n}> は前の子から n フレーム遅れて次の子を開始するので、フレーム 0 向けの入場効果がリストを順に流れます。
function Row({ label, y }: { label: string; y: number }) {
const frame = useCurrentFrame(); // 0 when this row starts
const p = progress(frame, 0, 20, Easings.easeOutExpo);
return <Text x={120 + 40 * (1 - p)} y={y} opacity={p}>{label}</Text>;
}
<Stagger each={4}>
{items.map((item, i) => <Row key={item} label={item} y={200 + i * 64} />)}
</Stagger>
拍に合わせる
useBeat({ bpm }) は現在の beat、bar、beatInBar、拍内の progress、拍ごとに 1 になり次の拍まで減衰する pulse を返します。音楽に合わせた点滅・拡縮・カットに使えます。offset で最初の拍をずらせます。
const { pulse } = useBeat({ bpm: 120 });
return <Rect width={40} height={40} cornerRadius={20} scale={1 + 0.3 * pulse} fill="#7cf29c" />;
キュー
useCue(cues) は at 順の { at, …data } オブジェクトのリストを受け取り、現在のキュー、index、開始からの frame、previous と next のキューを返します。キャプションの切り替え、カメラの移動、グラフの注釈をキューの列で表せます。cueAt(cues, frame) はコンポーネント外でも同じ計算を行います。
const active = useCue([
{ at: 0, text: 'Write it.' },
{ at: 45, text: 'Preview it.' },
{ at: 90, text: 'Ship it.' },
]);
if (!active) return null;
return <Text opacity={progress(active.frame, 0, 10)}>{active.cue.text}</Text>;
タイムコード
@celesta/react の frameToTimecode(frame, fps) は、HH:MM:SS:FF 形式の画面内時計を生成します。