動画の上に「ここに注目してほしい」を伝えるコールアウト——地図ピン・波紋・コネクター線・ラベルのセットをアニメーションで表示したい場面があります。
この記事では Claude Code + Remotion で、次の2種類のコールアウトを実装する方法を解説します。
- CalloutNavy(青系) — 同心円の波紋 → コネクター線 → ラベルがクリップイン
- CalloutRed(赤系) — ピンのスケールイン → 斜め線 → 矩形ボーダー描画 → テキスト
実装で踏んだ失敗(SVG が表示されない・interpolate のエラー)と解決策も含めて、再現性のある形でまとめています。
環境構築・プロジェクト作成・基本的な書き出し手順は「Claude Code × Remotion で動画字幕を自動化する」で解説しています。この記事では Callout コンポーネントの実装に絞って解説します。
Claude Code × Remotion で動画字幕を自動化する【環境構築から書き出しまで】
プロジェクト構成
[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 で書き出すとピンが豆粒サイズになります。目安は以下の通りです。
| 要素 | 1080p | 4K |
|---|---|---|
| PIN_R(ピン半径) | 22px | 48px |
| STROKE_W(線の太さ) | 3px | 6〜8px |
| LINE_LENGTH(コネクター長さ) | 160px | 288〜320px |
| ラベルの fontSize | 18px | 40px |
Remotion Studio でリアルタイム座標調整
ここが今回のメインです。コールアウトを「映像のどの位置に置くか」は、実際の映像を見ながら数値を調整する必要があります。
Studio の起動
npx remotion studio
ブラウザで http://localhost:3000 が開きます。
Cmd(Ctrl) + T で、ターミナルを2つ開いておくとスムーズです。
ターミナル①:npx remotion studio(起動したまま)
ターミナル②:claude(Claude Code を起動)
ファイルを保存するたびにブラウザが自動リロードされるため、Claude Code で修正 → Studio でリアルタイム確認という高速なサイクルを回せます。
Preview コンポジションで背景動画と重ねて確認
透過コンポジションは背景がチェッカー柄になるため、実際の映像と重ねて確認するには Root.tsx に Preview コンポジションを追加します。
// 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 で動画字幕を自動化する【環境構築から書き出しまで】
Gemini×Remotion でテロップを作る【Telop.tsx の実装とフォント設定】
Remotion で LottieFiles のアニメーションを使う【矢印・アイコンをオーバーレイ動画に組み込む】

