Remotion の interpolate() 完全解説 — 値のマッピングとアニメーション制御
Remotion の interpolate() 完全解説 — 値のマッピングとアニメーション制御
Remotionでアニメーションを作るとは、つまり「ある数値を別の数値に変換する」ことです。フレーム0ではタイトルが透明で、フレーム20では完全に表示されている。フレーム30ではカードが画面外にあり、フレーム60では定位置に収まっている。アニメーションの本質はすべて数値の写像であり、その中心にあるのが interpolate() 関数です。
この記事では、interpolate() のAPIを基礎から体系的に解説します。引数の意味、extrapolateLeft / extrapolateRight オプションの使い分け、イージング関数との組み合わせ、実務で頻出するパターン、そして初学者がはまりやすいミスまで、コードを動かしながら理解できるよう丁寧に説明します。
interpolate() が何をするのか
interpolate() はある数値範囲を別の数値範囲に線形マッピングする関数です。与えるのは3つの情報です。
- 入力値 — ほぼ常にその瞬間のフレーム番号
- 入力レンジ — 対象とするフレーム番号の範囲
- 出力レンジ — 入力に対応させたい値の範囲
直感的なモデルとして「定規」を思い浮かべてください。入力レンジが定規上の2つの目盛り(たとえばフレーム0とフレーム30)を指定します。出力レンジはその目盛りに対応する値(たとえばopacityの0と1)を指定します。フレーム15 — ちょうど中間点 — では interpolate() は0.5を返します。
この単純な仕組みが、Remotionのほぼすべてのアニメーションの土台になっています。
基本的なAPI構成
import { interpolate } from 'remotion';
interpolate(
value, // number — 入力値(多くの場合はuseCurrentFrame()の戻り値)
inputRange, // number[] — 例: [0, 30]
outputRange, // number[] — 例: [0, 1]
options? // オプションオブジェクト
)
inputRange と outputRange は同じ要素数でなければなりません。また inputRange は単調増加(各値が前の値以上)である必要があります。
最もシンプルなフェードインの実装を見てみましょう。
import { interpolate, useCurrentFrame } from 'remotion';
export const FadeIn: React.FC = () => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateRight: 'clamp',
});
return (
<div style={{ opacity }}>
こんにちは、Remotion
</div>
);
};
フレーム0では opacity が0、フレーム10では0.5、フレーム20では1になります。フレーム20以降は clamp によって1に固定されます。
extrapolateLeft と extrapolateRight の使い分け
デフォルトでは、interpolate() は入力レンジの境界を超えた部分でも線形の傾きを延長し続けます。これが 'extend'(デフォルト)の挙動です。たとえば入力レンジが [0, 20] でフレームが40の場合、opacityに対して 2 という無効な値が返ってきます。
これが extrapolateRight: 'clamp' をほぼ必ず書く理由です。入力が右境界を超えたら出力をそこで止める、という指示です。
両オプションで使用できる値は次のとおりです。
| 値 | 挙動 |
|---|---|
'extend' | 境界を超えても線形の傾きを延長する(デフォルト) |
'clamp' | 境界値で出力を固定する |
'wrap' | 入力をレンジの始点に折り返す(ループアニメーションで使用) |
'identity' | レンジ外では入力値をそのまま返す |
実務では 'clamp' が9割以上のケースで正解です。両端をクランプする典型的な書き方を示します。
const opacity = interpolate(frame, [10, 30], [0, 1], {
extrapolateLeft: 'clamp', // フレーム10より前 → opacity は 0 のまま
extrapolateRight: 'clamp', // フレーム30以降 → opacity は 1 のまま
});
extrapolateLeft を省略すると、フレーム10より前でopacityが負の値になるサイレントバグが発生します。視覚的に確認しづらいため注意が必要です。
イージング関数 — 動きに表情をつける
線形補間は計算として正確ですが、見た目には機械的です。一定速度で現れるタイトルは「動いている」という印象を与えますが、「生きている」とは感じさせません。
Remotionは Easing ユーティリティ(React Nativeから再エクスポート)を提供しており、標準的なイージング関数を揃えています。オプションオブジェクトの easing キーに渡して使います。
import { interpolate, Easing, useCurrentFrame } from 'remotion';
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateRight: 'clamp',
easing: Easing.ease,
});
イージング関数は正規化された入力(0〜1の進捗値)に対して適用されます。つまり interpolate() は内部でフレーム番号を0〜1の進捗に変換し、それをイージング関数に通してから出力レンジにマッピングします。
よく使うイージング関数
// なめらかな加減速 — 最も汎用的
easing: Easing.ease
// より大きな加減速
easing: Easing.inOut(Easing.cubic)
// 速い開始・遅い終了(ボールが転がって止まるイメージ)
easing: Easing.out(Easing.quad)
// 遅い開始・速い終了(勢いがついていくイメージ)
easing: Easing.in(Easing.cubic)
// キュービックベジェ(CSS の cubic-bezier 相当)
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0)
// 行き過ぎてから戻るエラスティック
easing: Easing.elastic(1)
// 着地時にバウンド
easing: Easing.bounce
Easing.elastic や Easing.bounce は0〜1の範囲を超える値を返すことがあります。これは意図的な設計で、行き過ぎ(オーバーシュート)がエフェクトの本体です。このため、これらとともに clamp を使うとバウンドが消えてしまいます。clampの使用タイミングは意識的に選んでください。
よく使うアニメーションパターン
フェードイン
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
フェードアウト
const opacity = interpolate(frame, [40, 60], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
左からスライドイン
const translateX = interpolate(frame, [0, 25], [-200, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.cubic),
});
// JSXで: style={{ transform: `translateX(${translateX}px)` }}
スケールアップ(ポップイン)
const scale = interpolate(frame, [0, 20], [0.5, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.back(1.5)),
});
// style={{ transform: `scale(${scale})` }}
Easing.back() は1をわずかに超えてから戻る動きを生み出し、要素が「パチッとはまる」感覚をもたらします。
回転しながら登場
const rotate = interpolate(frame, [0, 30], [-15, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.quad),
});
// style={{ transform: `rotate(${rotate}deg)` }}
複数の interpolate() を組み合わせたアニメーション
実際の制作では、複数のプロパティを異なるタイミングで動かすことがほとんどです。プロパティごとに interpolate() を1回呼び出し、それぞれ独立した入力レンジを設定するのが基本パターンです。
export const AnimatedCard: React.FC = () => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: 'clamp' });
const translateY = interpolate(frame, [0, 20], [40, 0], {
extrapolateRight: 'clamp',
easing: Easing.out(Easing.cubic),
});
const scale = interpolate(frame, [5, 25], [0.9, 1], { extrapolateRight: 'clamp' });
return (
<div
style={{
opacity,
transform: `translateY(${translateY}px) scale(${scale})`,
}}
>
カードのコンテンツ
</div>
);
};
3つのプロパティを微妙にずらしたレンジで動かすことで、単一プロパティのトゥイーンより豊かな入場演出が生まれます。
また、2要素より多い配列を渡すことで1回の呼び出しで複数ステージのアニメーションを記述できます。
// フェードイン → ホールド → フェードアウトを1行で
const opacity = interpolate(
frame,
[0, 20, 60, 80],
[0, 1, 1, 0],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
条件分岐なしで複雑なタイムラインを表現できる、非常に実用的な書き方です。
useCurrentFrame との連携
interpolate() は副作用のない純粋な関数です。useCurrentFrame() は現在のフレーム番号をReactコンポーネントに提供するフックで、この2つがフレームベースアニメーションの核心を成します。
import { interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
export const ProgressBar: React.FC = () => {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
// コンポジション全体の長さにわたって0%→100%へ進行
const progress = interpolate(frame, [0, durationInFrames], [0, 100], {
extrapolateRight: 'clamp',
});
return (
<div style={{ width: '100%', height: 8, background: '#eee', borderRadius: 4 }}>
<div
style={{
width: `${progress}%`,
height: '100%',
background: '#0066ff',
borderRadius: 4,
}}
/>
</div>
);
};
useVideoConfig() は durationInFrames、fps、width、height を返します。durationInFrames を入力レンジの上限に使うと、コンポジションの長さが変わっても自動的に追従するアニメーションを作れます。
注意点として、useCurrentFrame() はReactコンポーネントのトップレベルでのみ呼び出せます。コールバック・ループ・条件分岐の中では呼べません。一方、interpolate() 自体は純粋な関数なのでどこでも呼び出し可能です。
よくあるミスと回避策
1. extrapolateRight のクランプ忘れ
最も頻繁なバグです。interpolate(frame, [0, 30], [0, 1]) と書いてopacityが常に0〜1の間に収まると思い込むケースです。フレーム30以降も値は増加し続けます。意図的に延長したい場合を除き、extrapolateRight: 'clamp' を書く習慣をつけてください。
2. inputRange を降順で書く
入力レンジは単調増加でなければなりません。[30, 0] のように書くとエラーになります。アニメーションを逆向きにしたい場合は、outputRange を逆にします。
// フレーム0→30でフェードアウト(正しい書き方)
const opacity = interpolate(frame, [0, 30], [1, 0], { extrapolateRight: 'clamp' });
3. inputRange と outputRange の要素数が違う
必ず同じ要素数が必要です。[0, 15, 30] と [0, 1] のように組み合わせると実行時エラーになります。
4. Easing.elastic / Easing.bounce にclampをかけてしまう
これらのイージングは意図的に出力レンジ外の値を返します。clampをかけるとバウンド・オーバーシュートが消えてしまい、エフェクトが台無しになります。トランスフォーム系のプロパティであれば範囲外の値でも問題ないことが多いため、clampなしで使うことを検討してください。
5. extrapolateLeft の存在を忘れる
右側はクランプしても左側を忘れるパターンです。入力レンジが [10, 30] の場合、フレーム0〜9ではデフォルトの 'extend' が適用され、opacityが負になる可能性があります。両端をクランプするのが原則です。
よくある質問
Q: interpolate() で色をアニメーションさせられますか?
RGBやhex値の補間には interpolateColors() という専用関数があります。通常の interpolate() は数値専用で、文字列の 'red' や '#ff0000' は扱えません。
Q: interpolate() と spring() の違いは何ですか?
interpolate() はフレームと値を確定的にマッピングします。「フレーム20で必ずopacityが1になる」という制御が可能です。spring() は物理シミュレーションなので、持続時間はパラメータから自動的に決まります。タイミングを厳密にコントロールしたい場面では interpolate()、自然な物理的な動きが欲しい場面では spring() が向いています。
Q: フレーム番号以外の値を入力として渡せますか?
はい、interpolate() は任意の数値を受け取れます。スクロールオフセット、パーセンテージ、別の interpolate() の出力など、何でも渡せます。
Q: アニメーションが最初か最後のフレームでちらつきます。なぜですか?
ほとんどの場合、extrapolateLeft: 'clamp' の不足が原因です。入力レンジがフレーム5から始まる場合、フレーム0では 'extend' のデフォルト挙動でopacityが負になり、視覚的なアーティファクトが生じます。
Q: マルチストップ(3つ以上の要素)はどれくらい使っていいですか?
明確な上限はありませんが、6〜8ストップを超えると読みにくくなります。それ以上の複雑さが必要な場合は、コンポジション内で <Sequence> を使って区間を分割する設計を検討してください。
Q: 毎フレーム再レンダリングされるのでパフォーマンスが心配です。
Remotionは各フレームをReactコンポーネントのスナップショットとしてレンダリングする設計です。useCurrentFrame() がフレームごとに異なる値を返すため再評価が走りますが、これは仕様です。interpolate() 自体は純粋な算術演算なので処理速度は問題になりません。
Q: RenderCompのテンプレートでも interpolate() を使えますか?
はい。RenderCompのテンプレートはすべて標準的なRemotionコンポジションです。テンプレートコンポーネント内のどこでも interpolate() を呼び出すことができ、フレームレンジをpropsとして渡すことで再利用可能なアニメーションパーツを作成できます。
まとめ
interpolate() はRemotionのAPI群の中で最も使用頻度が高い関数です。スプリング、シーケンス、音量カーブ — あらゆるアニメーション制御は、結局のところ数値を別の数値に時間をかけて変換するという操作に行き着きます。
押さえておくべきポイントを整理します。
- 意図的な理由がない限り、常に両端をクランプする
- 複数ステージのアニメーションにはif/else分岐ではなくマルチストップ配列を使う
- プロパティごとに独立したレンジを設定して複数の
interpolate()を重ねる - イージング関数でスプリングに頼らずとも動きに表情をつけられる
useVideoConfig()のdurationInFramesを使ってレンジをコンポジション長に相対的に保つ
interpolate() の使い方を体で覚えると、Remotionのコードを読み書きする速度が格段に上がります。より実践的なアニメーションを手早く実装したい方は、RenderComp のテンプレートライブラリもご活用ください。この記事で解説した設計原則を実際のプロジェクトコードで確認できます。
この記事はRenderCompが提供しています — プロダクション品質のRemotionアニメーションテンプレートライブラリ。