Remotion スプリングアニメーション完全ガイド
Remotion スプリングアニメーション完全ガイド
モバイルアプリのアニメーションが「なぜこんなに気持ちいいのか」と感じたことはないでしょうか。その答えのほとんどは、スプリング物理演算にあります。スプリングアニメーションは、AからBへ一定速度で移動するのではなく、物理的なバネのように加速し、ターゲットをわずかに行き過ぎてから戻り、ゆっくりと静止します。この動きが「重さ」と「勢い」を感じさせるのです。
RemotionはReactベースのプログラマブル動画ライブラリで、spring()関数によってスプリングアニメーションをネイティブサポートしています。この記事では、spring()の仕組み、パラメータの調整方法、実務で使えるロゴ入場アニメーションや数値カウンターの実装例までを体系的に解説します。
スプリングアニメーションとは何か
CSS の ease-in-out やベジェ曲線は、開始・終了時刻があらかじめ決まっている「イージング」です。アニメーションのオブジェクトはゴールとタイミングを最初から知っており、その通りに動きます。洗練されてはいますが、機械的に見えることもあります。
スプリングアニメーションは「バネのシミュレーション」です。ゴールに向かって加速し、勢い余って行き過ぎ(オーバーシュート)、引き戻されて振動しながら静止します。この動きの特徴は次の3点です。
- 直感的にチューニングできる — ベジェ曲線のハンドル操作ではなく、質量・硬さ・減衰という物理パラメータで調整する
- 持続時間はパラメータから生まれる — 「何ミリ秒で終わらせるか」ではなく「どんな動きにするか」を指定する
- 動きに生命感が宿る — オーバーシュートと静止のプロセスが視聴者に重量感とエネルギーを伝える
動画制作においては、タイトルの登場、ロゴ入場、統計カウンター、プログレスバーなど、「ただ動く」ではなく「エネルギーを感じさせる」場面で特に効果的です。
バネ物理演算の3パラメータ
コードを書く前に、3つのパラメータの直感的な意味を押さえておきましょう。微分方程式の理解は不要です。
mass(質量)
バネに取り付けられた物体の重さです。重いほど慣性が大きく、加速も減速も遅くなります。デフォルトは 1。1 より小さくすると動きが速くなり、大きくするとオーバーシュートが大きくなります。
stiffness(硬さ)
バネそのものの張力、つまりどれだけ強くオブジェクトをゴールへ引き戻すかです。デフォルトは 100。高くするほどパキッとした素早い動きになり、低くするほどフワッとした緩やかな動きになります。
damping(減衰)
ブレーキ力です。振動を抑える摩擦にあたります。デフォルトは 10。高くするとバウンドなしのスムーズな動き、低くするとバウンドが多くなります。
組み合わせの考え方
高stiffness+低dampingは「素早く弾む」、低stiffness+高dampingは「ゆっくり柔らかく収まる」という傾向があります。Remotion公式のインタラクティブエディタ springs.remotion.dev でスライダーを動かしてカーブを確認しながら調整するのが最も効率的です。
spring() 関数 — 基本API
spring() は 'remotion' パッケージから直接インポートします。追加パッケージは不要です。
import { spring, useCurrentFrame, useVideoConfig } from 'remotion';
関数シグネチャ
spring({
frame, // number — 現在フレーム(useCurrentFrame()の戻り値)
fps, // number — フレームレート(useVideoConfig()から取得)
config?: {
mass?: number; // デフォルト: 1
stiffness?: number; // デフォルト: 100
damping?: number; // デフォルト: 10
overshootClamping?: boolean; // デフォルト: false
};
from?: number; // デフォルト: 0 — アニメーション開始値
to?: number; // デフォルト: 1 — アニメーション終了値
durationInFrames?: number; // このフレーム数以内に収める
delay?: number; // 開始を遅らせるフレーム数
})
戻り値
spring() は number を返します。デフォルトでは 0 から 1 に向かって動きます。from と to を指定すれば、その値の間で直接動かせます。
最小の使用例
import { spring, useCurrentFrame, useVideoConfig } from 'remotion';
export const MyComp: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scale = spring({
frame,
fps,
from: 0,
to: 1,
config: { mass: 1, stiffness: 120, damping: 12 },
});
return (
<div style={{ transform: `scale(${scale})` }}>
Hello
</div>
);
};
フレーム0では scale: 0(非表示)。スプリングが 1 を少し超えてオーバーシュートし、跳ね返って 1 に静止します。このわずかなバウンドが動きに生命感を与えます。
spring() と interpolate() の組み合わせ
spring() は数値を返しますが、実際にはCSSのpx値や角度に変換する必要があります。Remotionの interpolate() 関数がその役割を担います。
import { spring, interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
export const SlideIn: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// spring は 0 → 1 を出力
const progress = spring({ frame, fps, config: { damping: 14, stiffness: 80 } });
// 0→1 を 200px→0px にマッピング(右から左へスライドイン)
const translateX = interpolate(progress, [0, 1], [200, 0]);
// 0→1 を opacity 0→1 にマッピング
const opacity = interpolate(progress, [0, 1], [0, 1]);
return (
<div
style={{
transform: `translateX(${translateX}px)`,
opacity,
}}
>
スライドイン
</div>
);
};
spring() で 0〜1 の「ドライバー値」を作り、interpolate() で具体的なCSS値にマッピングするパターンが、Remotionにおける定石です。コードが読みやすく、複数プロパティへの適用も一元管理できます。
実装例1 — バウンドするロゴ入場
ロゴの登場演出は、スプリングアニメーションの最も定番な用途です。スケールとフェードインを組み合わせた完成例を示します。
import { spring, interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
export const LogoEntrance: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const enter = spring({
frame,
fps,
config: {
mass: 0.8,
stiffness: 100,
damping: 10,
},
});
const scale = interpolate(enter, [0, 1], [0.4, 1]);
const opacity = interpolate(enter, [0, 1], [0, 1], {
extrapolateRight: 'clamp',
});
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
background: '#0f0f0f',
}}
>
<img
src="logo.svg"
style={{
transform: `scale(${scale})`,
opacity,
width: 240,
}}
/>
</div>
);
};
mass: 0.8 で軽く弾む感触を演出しています。extrapolateRight: 'clamp' をopacityに適用しているのは、スプリングのオーバーシュート中に opacity が 1 を超えないようにするためです。
実装例2 — スプリング駆動のアニメーションカウンター
数値が弾むように増加していくカウンターは、線形なアニメーションよりはるかに印象的です。スプリングの「勢いよく近づいて、ゆっくり静止する」特性が自然な数値アニメーションを生み出します。
import { spring, interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
interface CounterProps {
targetValue: number;
label: string;
}
export const SpringCounter: React.FC<CounterProps> = ({ targetValue, label }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame,
fps,
config: {
mass: 1,
stiffness: 60,
damping: 12,
},
});
const displayValue = Math.round(
interpolate(progress, [0, 1], [0, targetValue])
);
return (
<div style={{ textAlign: 'center', fontFamily: 'sans-serif', color: '#fff' }}>
<div style={{ fontSize: 96, fontWeight: 700, lineHeight: 1 }}>
{displayValue.toLocaleString('ja-JP')}
</div>
<div style={{ fontSize: 24, marginTop: 8, opacity: 0.7 }}>
{label}
</div>
</div>
);
};
stiffness: 60 の低めの設定により、序盤はゆっくり加速し、終盤にパッと目標値へ近づきます。オーバーシュートで一瞬目標値を超えてから静止するため、「勢いよく達成した感」が生まれます。
durationInFrames で継続時間を制御する
デフォルトでは spring() は物理演算が自然に収束するまで動き続けます。特定のフレームで確実に完了させたい場合は durationInFrames を使います。
const anim = spring({
frame,
fps,
durationInFrames: Math.round(fps * 0.5), // 0.5秒以内に完了
config: { stiffness: 200, damping: 20 },
});
また、'remotion' から measureSpring() をインポートすると、指定したコンフィグが何フレームで収束するかを数値で取得できます。アニメーションを連鎖させるときに役立ちます。
import { measureSpring } from 'remotion';
const frames = measureSpring({
fps: 30,
config: { mass: 1, stiffness: 100, damping: 10 },
});
// 収束までのフレーム数が返る
スタガードアニメーション(時差入場)
複数の要素を順番に登場させる「時差入場」は、delay パラメータで簡単に実装できます。
const items = ['設計する', '開発する', 'リリースする'];
{items.map((item, i) => {
const enter = spring({
frame,
fps,
delay: i * 6, // 6フレームずつずらす
config: { damping: 14, stiffness: 90 },
});
const translateY = interpolate(enter, [0, 1], [40, 0]);
return (
<div
key={item}
style={{
transform: `translateY(${translateY}px)`,
opacity: enter,
}}
>
{item}
</div>
);
})}
インデックス i に定数を掛けた値を delay に渡すだけで、要素ごとに時差を設けられます。
よくある間違いと対処法
fps を useVideoConfig() から取得しない
spring() には frame と fps の両方が必要です。fps をハードコードすると、コンポジションのフレームレートを変更したときにアニメーションの速度がズレます。必ず useVideoConfig() から取得してください。
const { fps } = useVideoConfig(); // 必ずこの形で
境界値のある値に overshootClamping を設定しない
プログレスバーや不透明度など、特定の範囲を超えてはいけない値には overshootClamping: true を設定するか、interpolate() に { extrapolateRight: 'clamp' } を渡してください。スプリングのオーバーシュートでプログレスバーが一瞬103%になるのはバグに見えます。
damping を極端に下げすぎる
damping が 6 以下になると振動サイクルが多くなり、テキストやUIが「壊れている」ように見えます。ほとんどの用途では 10〜14 からスタートし、意図的に弾ませたい場合にのみ下げましょう。
Spring Editor を使わない
パラメータを感覚で調整するのは非効率です。springs.remotion.dev でリアルタイムにカーブを確認しながら決定し、その値をコードに貼り付けるのが最速です。
よくある質問(FAQ)
Q: spring() は 'remotion' と '@remotion/core' どちらからインポートすべきですか?
どちらでも動作します。'remotion' は '@remotion/core' のすべてをre-exportしているため、import { spring } from 'remotion' がシンプルで一般的な書き方です。
Q: <Sequence> の中でもスプリングアニメーションは使えますか?
使えます。<Sequence> は useCurrentFrame() の値をオフセットするため、スプリングはシーケンスの開始フレームを 0 として自動的に動き始めます。追加の設定は不要です。
Q: バウンドを正確に2回にする方法はありますか?
バウンド回数を直接指定するパラメータはありません。damping を下げながらSpring Editorで波形を確認し、バウンド回数を目視で調整します。デフォルトの mass と stiffness で damping を 5〜7 程度にすると、2〜3回のバウンドが得られる傾向があります。
Q: 1つのスプリングで複数のプロパティを同時に動かせますか?
推奨される方法です。spring() の戻り値(0〜1)をドライバーとして、interpolate() で複数のCSS値に変換します。すべてのプロパティが同じ物理演算に従って動くため、動きが自然にまとまります。
Q: Remotion Studioのプレビューでは正常に見えるのに、レンダリング後に動きがおかしくなります。
ほぼ確実にフレームレートの不一致が原因です。fps をハードコードせず、useVideoConfig() から取得してください。Studio が30fps でも、コンポジション設定が60fpsならレンダリング結果の速度が倍になります。
Q: スプリングアニメーションをループさせたい場合は?
spring() 自体にループ機能はありませんが、frame % loopDuration で周期的なフレーム値を計算し、それを spring() に渡すことで擬似ループを作れます。ただし、各ループが独立したスプリングになるため、ループ境界での滑らかさは別途調整が必要です。
まとめ
Remotion の spring() はシンプルなAPIながら、映像に生命感をもたらす最も強力な手法の一つです。基本的な考え方を整理します。
spring()は物理演算に基づいてfromからtoへ動く数値を返すinterpolate()と組み合わせて、任意のCSS値へマッピングするmass・stiffness・dampingで動きのキャラクターを調整する- パラメータ調整はSpring Editorを活用する
スプリングアニメーションをマスターすると、ほぼすべてのコンポジションで使いたくなるはずです。タイトル、ロゴ、カウンター、UI要素——すべてがリニアな動きより格段に印象的になります。
本番品質のスプリングアニメーションテンプレートは RenderComp で公開中 →
RenderCompのすべてのテンプレートは、調整済みのスプリング設定を使ってゼロから構築されています。ロゴ入場・統計カウンター・ローワーサード・その他多数——ソースコードは編集可能な状態で提供しています。