基本コンポーネント

ツリーを組み立てる骨格部分。<Project> > <TimeLine> > <Clip> > シーン本体、という階層になる。

ルート

<Project>

描画のルート。PROJECT_SETTINGSwidth/height に 合わせた固定サイズの描画面を提供する。必ず一番外側に置く

project.tsx
import { Project } from "../src/lib/project"

export const PROJECT = () => (
  <Project>
    {/* 中身 */}
  </Project>
)

<TimeLine>

子要素として並んだ <Clip> たちをタイムラインに登録するコンテナ。 Studio のタイムライン UI に表示される範囲はこれが管理している。

<Project>
  <TimeLine>
    {/* <Clip> / <ClipSequence> を並べる */}
  </TimeLine>
</Project>

クリップ

<Clip>

タイムライン上の1つの箱。「いつから」「何フレーム分」描画するかを表す。

子要素に <Video>useAnimation がいると、その長さが Clip に「報告」されて勝手に長さが決まる。明示したい時だけ duration を指定する と覚えるのが楽。

// 動画の長さに自動で合わせる
<Clip label="Intro">
  <Video video="assets/intro.mp4" />
</Clip>

// 明示的に 3.5 秒
<Clip label="Title" duration={seconds(3.5)}>
  <TitleScene />
</Clip>

// 開始位置を指定
<Clip start={seconds(2.6)} duration={seconds(3)} label="Transition1">
  <Transition1 />
</Clip>
非アクティブ時は描画されない

現在フレームが Clip の範囲外になると、その中身は DOM ごと非表示になる。 Clip 内部の useState はマウント/アンマウントで初期化されるので、 状態を Clip をまたいで保持するつもりで書くと壊れる。

<ClipSequence>

中の <Clip>同じレーンで前後に連結する。 前のクリップの長さに合わせて自動で start がずれる。

<ClipSequence>
  <Clip label="Intro"    duration={seconds(3)}><Intro /></Clip>
  <Clip label="Features" duration={seconds(5)}><Features /></Clip>
  <Clip label="Outro"    duration={seconds(2)}><Outro /></Clip>
</ClipSequence>

<ClipStatic>

startend(フレーム)を直接指定する低レベル版。 境界を厳密に制御したいときに使う。

<ClipStatic start={0} end={119} label="Custom Range">
  <MyScene />
</ClipStatic>

<Serial>

<ClipStatic> の連結版。長さ(end - start)を保ったまま 前から順に並べてくれる。

<Serial>
  <ClipStatic start={0} end={89} label="A"><SceneA /></ClipStatic>
  <ClipStatic start={0} end={59} label="B"><SceneB /></ClipStatic>
</Serial>

クリップ情報を読むフック

シーン側から自分が今どの Clip にいるかを知りたい時に使う。

フック戻り値使い所
useClipStart()絶対開始フレーム絶対フレームを計算したい時
useClipRange(){ start, end }範囲全体が欲しい時
useClipId()Clip の id稀。デバッグ用
useClipDepth()ネスト深度背景クリップ等の判定
useClipActive()boolean今 Clip がアクティブか

フレーム関連

useCurrentFrame()

現在のフレーム数を返す。Clip 内では Clip 開始からの相対フレームになる。 絶対フレームが欲しい時は useGlobalCurrentFrame()

const frame = useCurrentFrame()        // Clip 内なら相対
const global = useGlobalCurrentFrame() // プロジェクト全体の絶対フレーム

seconds(n)

秒をフレーム数に変換する。PROJECT_SETTINGS.fps を見るので、 fps を変えるだけで全体のテンポを保ったまま切り替えられる。

const introFrames = seconds(3.5) // fps=60 なら 210

レイアウト

<FillFrame>

画面全体を覆う position: absolute; inset: 0 なコンテナ。 中身は flex columnシーンの一番外側でほぼ毎回使う

<FillFrame style={{ alignItems: "center", justifyContent: "center" }}>
  <Title />
</FillFrame>
Tips: 通常の React + CSS が普通に効く

シーンの中身は普通の React なので、@emotion/styledstyled.div を作ったり、grid/flex を使ったりが普通にできる。
CSS は「フレームに依存しない部分」を担当すると考えると整理しやすい。
時間で変わる部分は useVariable + useAnimation