動画の上に「ここに注目してほしい」を伝えるコールアウト——地図ピン・波紋・コネクター線・ラベルのセットをアニメーションで表示したい場面があります。

この記事では Claude Code + Remotion で、次の2種類のコールアウトを実装する方法を解説します。

  • CalloutNavy(青系) — 同心円の波紋 → コネクター線 → ラベルがクリップイン
  • CalloutRed(赤系) — ピンのスケールイン → 斜め線 → 矩形ボーダー描画 → テキスト

実装で踏んだ失敗(SVG が表示されない・interpolate のエラー)と解決策も含めて、再現性のある形でまとめています。

環境構築・プロジェクト作成・基本的な書き出し手順は「Claude Code × Remotion で動画字幕を自動化する」で解説しています。この記事では Callout コンポーネントの実装に絞って解説します。

Claude Code × Remotion で動画字幕を自動化する【環境構築から書き出しまで】 Claude CodeとRemotionを組み合わせて、動画の字幕(キャプション)をProRes 4444透過動画として生成するワークフローを解説。content.tsでタイムスタンプを一元管理し、マニュアル動画・PV動画・YouTube動画など幅広い動画制作...  続きを読む

    プロジェクト構成

    [project-name]/
    ├─ src/
    │     ├─ components/
    │     │     ├─ CalloutNavy.tsx    # ← この記事で作成
    │     │     └─ CalloutRed.tsx     # ← この記事で作成
    │     ├─ content.ts
    │     └─ Root.tsx
    ├─ output/
    │     ├─ calloutNavy.mov
    │     └─ calloutRed.mov
    └─ public/
           └─ video/
                └─ background.mp4    # 座標確認用の背景動画
    

    SVG ではなく div で描く

    最初に直面する問題として、Remotion のレンダリングエンジンでは overflow: visible + ゼロサイズ SVG が無視されます

    // ❌ Remotion では表示されない
    <svg width={0} height={0} style={{ overflow: "visible" }}>
      <circle cx={0} cy={0} r={48} fill="#213980" />
    </svg>
    

    サイズを大きくしても改善しないケースがあります。解決策は div + CSS への全面切り替えです。

    // ✅ 確実に表示される
    
    // ピン(塗りつぶし円)
    <div style={{
      width: 96,
      height: 96,
      borderRadius: "50%",
      backgroundColor: "#213980",
    }} />
    
    // 波紋(線のみの円)
    <div style={{
      width: 192,
      height: 192,
      borderRadius: "50%",
      border: "8px solid #213980",
      opacity: rippleOpacity,
      transform: `scale(${rippleScale})`,
    }} />
    

    コネクター線も width を動的に変化させるだけで「線が伸びる」アニメーションを実現できます。


    段階的アニメーションのチェイン

    「波紋 → コネクター線 → ラベルクリップイン」のような順次アニメーションは、interpolate を使わず手動計算がおすすめです。

    interpolate は入力範囲の2点が同値になるとランタイムエラーを起こします(例:あるフェーズが開始前のフレームで範囲が縮退するケース)。

    代わりに次のパターンを使います。

    const PHASE_A_DUR = 15; // 波紋
    const PHASE_B_DUR = 20; // コネクター線
    const PHASE_C_DUR = 12; // ラベル
    
    const f = useCurrentFrame() - startFrame;
    
    // Phase A
    const aProg = Math.min(1, Math.max(0, f) / PHASE_A_DUR);
    
    // Phase B(A 完了後に開始)
    const bStart = PHASE_A_DUR;
    const bProg = Math.min(1, Math.max(0, f - bStart) / PHASE_B_DUR);
    
    // Phase C(B 完了後に開始)
    const cStart = bStart + PHASE_B_DUR;
    const cProg = Math.min(1, Math.max(0, f - cStart) / PHASE_C_DUR);
    
    // ease-out cubic(手動)
    const easeOut = (p: number) => 1 - Math.pow(1 - p, 3);
    

    prog は 0〜1 の進捗値なので、easeOut(aProg) のようにイージングを適用してスタイルに使います。


    clipPath によるクリップインアニメーション

    ラベルの「左から右へ出現する」演出には clipPath が便利です。transform を使わないためレイアウト崩れが起きません。

    // 左から右へクリップイン
    <div style={{
      clipPath: `inset(0 ${100 - labelProg * 100}% 0 0)`,
      backgroundColor: "#213980",
      color: "#FFFFFF",
      padding: "12px 24px",
      borderRadius: 8,
      whiteSpace: "nowrap",
    }}>
      ラベルテキスト
    </div>
    

    テキスト・背景どちらにも使えます。


    CalloutRed:矩形ボーダー描画アニメーション

    「4辺が順次描かれる」矩形ボーダーアニメーションは、周囲長から各辺の描画長を計算する方法で実装します。SVG の stroke-dashoffset より確実に動作します。

    const BOX_W = 560;
    const BOX_H = 160;
    const BORDER_W = 6;
    const COLOR = "#B31732";
    
    const perimeter = (BOX_W + BOX_H) * 2;
    const drawn = perimeter * borderProg; // 0〜1 の進捗から描画済み長さを計算
    
    const topLen    = Math.min(drawn, BOX_W);
    const rightLen  = Math.min(Math.max(0, drawn - BOX_W), BOX_H);
    const bottomLen = Math.min(Math.max(0, drawn - BOX_W - BOX_H), BOX_W);
    const leftLen   = Math.min(Math.max(0, drawn - BOX_W * 2 - BOX_H), BOX_H);
    
    // 各辺を個別 div で描画
    <div style={{ position: "absolute", left: 0, top: 0,
      width: topLen, height: BORDER_W, backgroundColor: COLOR }} />
    <div style={{ position: "absolute", right: 0, top: 0,
      width: BORDER_W, height: rightLen, backgroundColor: COLOR }} />
    <div style={{ position: "absolute", right: 0, bottom: 0,
      width: bottomLen, height: BORDER_W, backgroundColor: COLOR }} />
    <div style={{ position: "absolute", left: 0, bottom: 0,
      width: BORDER_W, height: leftLen, backgroundColor: COLOR }} />
    

    4K 制作時のサイズ感

    1080p の値のまま 4K で書き出すとピンが豆粒サイズになります。目安は以下の通りです。

    要素1080p4K
    PIN_R(ピン半径)22px48px
    STROKE_W(線の太さ)3px6〜8px
    LINE_LENGTH(コネクター長さ)160px288〜320px
    ラベルの fontSize18px40px

    Remotion Studio でリアルタイム座標調整

    ここが今回のメインです。コールアウトを「映像のどの位置に置くか」は、実際の映像を見ながら数値を調整する必要があります。

    Studio の起動

    npx remotion studio
    

    ブラウザで http://localhost:3000 が開きます。
    Cmd(Ctrl) + T で、ターミナルを2つ開いておくとスムーズです。

    ターミナル①:npx remotion studio(起動したまま)
    ターミナル②:claude(Claude Code を起動)
    

    ファイルを保存するたびにブラウザが自動リロードされるため、Claude Code で修正 → Studio でリアルタイム確認という高速なサイクルを回せます。

    Preview コンポジションで背景動画と重ねて確認

    透過コンポジションは背景がチェッカー柄になるため、実際の映像と重ねて確認するには Root.tsxPreview コンポジションを追加します。

    // Root.tsx
    import { AbsoluteFill, OffthreadVideo, staticFile } from "remotion";
    
    const Preview: React.FC = () => (
      <AbsoluteFill>
        <OffthreadVideo src={staticFile("video/background.mp4")} />
        <CalloutNavyComp pinX={2880} pinY={1620} />
      </AbsoluteFill>
    );
    

    必ず staticFile() を使ってください。 http://localhost:3000/video/... とハードコードすると、レンダリング時に「The browser threw an error while playing the video」エラーが出ます。

    Zod スキーマで Props パネルを有効化

    Studio の右パネルで pinX / pinY を GUI で編集するには、Zod スキーマの設定が必要です。defaultProps だけ追加しても「schema prop を追加してください」と表示されて機能しません。

    まずパッケージをインストールします。

    npx remotion add @remotion/zod-types zod
    

    次に Root.tsx にスキーマとラッパーコンポーネントを追加します。

    import { z } from "zod";
    import { zColor } from "@remotion/zod-types";
    
    // スキーマ定義
    const pinSchema = z.object({
      pinX: z.number(),
      pinY: z.number(),
    });
    type PinProps = z.infer<typeof pinSchema>;
    
    // ラッパーコンポーネント
    const CalloutNavyComp: React.FC<PinProps> = ({ pinX, pinY }) => (
      <AbsoluteFill>
        <CalloutNavy
          label="ラベルテキスト"
          pinX={pinX}
          pinY={pinY}
          direction="top"
          startFrame={0}
          endFrame={VIDEO_CONFIG.durationSeconds * VIDEO_CONFIG.fps}
        />
      </AbsoluteFill>
    );
    
    // Composition に schema と defaultProps を追加
    <Composition
      id="CalloutNavy"
      component={CalloutNavyComp}
      schema={pinSchema}
      defaultProps={{ pinX: 2880, pinY: 1620 }}
      durationInFrames={VIDEO_CONFIG.durationSeconds * fps}
      fps={fps}
      width={width}
      height={height}
    />
    

    defaultProps はリテラル(直書き)で書く必要があります。 変数参照(defaultProps={calloutDefaults} など)にすると警告が出ます。

    これで Studio の右パネルに pinX / pinY の数値入力欄が表示され、値を変更するとプレビューにリアルタイム反映されます。

    Studio 操作の基本

    操作方法
    フレームを1コマ送る キー
    任意フレームに移動タイムラインの数値を直接入力
    表示サイズを縮小右上の % ドロップダウンで 25% 等に変更
    Composition 切り替え左サイドバーで選択

    startFrame が途中のフレームの場合(例:00:11.29 = フレーム359)、フレーム0では何も表示されません。 タイムラインを該当フレーム付近に移動してください。座標確認中は startFrame={0} に一時変更しておくと便利です。


    推奨ワークフロー

    実際の作業では次の順番で進めると手戻りが少ないです。

    Step 1: 静的表示の確認
    まず backgroundColor: "red" の div だけ置いて、ピンが正しい位置に表示されることを確認します。

    Step 2: アニメーションなしで最終形を表示
    全要素を opacity: 1 で固定表示し、見た目を確認してからアニメーションを付けます。

    Step 3: 1フェーズずつアニメーションを追加
    ピン → 波紋 → 線 → ラベル の順に1要素ずつ実装し、各段階でプレビューします。

    Step 4: Studio で座標微調整
    Zod スキーマを設定し、右パネルから pinX / pinY を変更しながら位置を確定します。

    Step 5: 書き出し
    座標が確定したら startFrame を実際のタイムスタンプに戻してレンダリングします。


    透過動画の書き出し

    npx remotion render CalloutNavy output/calloutNavy.mov 
      --image-format=png 
      --pixel-format=yuva444p10le 
      --codec=prores 
      --prores-profile=4444 
      --muted
    

    注意点が2つあります。

    --image-format=png は必須です。省略すると次のエラーになります。

    TypeError: Pixel format was set to 'yuva444p10le' but the image format is not PNG.
    

    オプションは1行またはバックスラッシュで継続してください。各オプションを改行して別コマンドとして実行すると zsh: command not found: --image-format=png エラーになります。

    複数コンポジションを書き出す場合は、Claude Code にまとめて指示すると並列実行してくれます。

    CalloutNavy と CalloutRed を並列で書き出してください。
    npm run render:calloutNavy & npm run render:calloutRed
    

    まとめ・関連記事

    この記事では、div ベースの Callout アニメーション実装と Remotion Studio + Zod を使ったリアルタイム座標調整を解説しました。

    コードで管理できるため、複数のコールアウトを一括変更したい場合も content.ts の1箇所を直すだけで済みます。Claude Code との組み合わせで「ピンのサイズを大きくして」「波紋を3回繰り返して」といった修正も自然言語で対応できます。

    Claude Code × Remotion で動画字幕を自動化する【環境構築から書き出しまで】 Claude CodeとRemotionを組み合わせて、動画の字幕(キャプション)をProRes 4444透過動画として生成するワークフローを解説。content.tsでタイムスタンプを一元管理し、マニュアル動画・PV動画・YouTube動画など幅広い動画制作...  続きを読む Gemini×Remotion でテロップを作る【Telop.tsx の実装とフォント設定】 Remotionでテロップコンポーネント(Telop.tsx)を実装する方法を解説。日本語フォント・約物カーニング・フェードアニメーション・縦位置の切り替えなど、品質にこだわった実装を紹介。Geminiとのcontent.ts連携でタイムスタンプ生成も自動化で...  続きを読む Remotion で LottieFiles のアニメーションを使う【矢印・アイコンをオーバーレイ動画に組み込む】 RemotionにLottieFilesのアニメーション(矢印・アイコン等)を組み込む方法を解説。@remotion/lottieのインストールからJSON配置・カラー変更・フェードアニメーション・透過書き出しまで、動画オーバーレイへの実践的な活用方法を紹介し...  続きを読む

    参考リンク