remotion アニメーション spring react チュートリアル

Remotion スプリングアニメーション完全ガイド

Remotion スプリングアニメーション完全ガイド

モバイルアプリのアニメーションが「なぜこんなに気持ちいいのか」と感じたことはないでしょうか。その答えのほとんどは、スプリング物理演算にあります。スプリングアニメーションは、AからBへ一定速度で移動するのではなく、物理的なバネのように加速し、ターゲットをわずかに行き過ぎてから戻り、ゆっくりと静止します。この動きが「重さ」と「勢い」を感じさせるのです。

RemotionはReactベースのプログラマブル動画ライブラリで、spring()関数によってスプリングアニメーションをネイティブサポートしています。この記事では、spring()の仕組み、パラメータの調整方法、実務で使えるロゴ入場アニメーションや数値カウンターの実装例までを体系的に解説します。


スプリングアニメーションとは何か

CSS の ease-in-out やベジェ曲線は、開始・終了時刻があらかじめ決まっている「イージング」です。アニメーションのオブジェクトはゴールとタイミングを最初から知っており、その通りに動きます。洗練されてはいますが、機械的に見えることもあります。

スプリングアニメーションは「バネのシミュレーション」です。ゴールに向かって加速し、勢い余って行き過ぎ(オーバーシュート)、引き戻されて振動しながら静止します。この動きの特徴は次の3点です。

  • 直感的にチューニングできる — ベジェ曲線のハンドル操作ではなく、質量・硬さ・減衰という物理パラメータで調整する
  • 持続時間はパラメータから生まれる — 「何ミリ秒で終わらせるか」ではなく「どんな動きにするか」を指定する
  • 動きに生命感が宿る — オーバーシュートと静止のプロセスが視聴者に重量感とエネルギーを伝える

動画制作においては、タイトルの登場、ロゴ入場、統計カウンター、プログレスバーなど、「ただ動く」ではなく「エネルギーを感じさせる」場面で特に効果的です。


バネ物理演算の3パラメータ

コードを書く前に、3つのパラメータの直感的な意味を押さえておきましょう。微分方程式の理解は不要です。

mass(質量)

バネに取り付けられた物体の重さです。重いほど慣性が大きく、加速も減速も遅くなります。デフォルトは 11 より小さくすると動きが速くなり、大きくするとオーバーシュートが大きくなります。

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 に向かって動きます。fromto を指定すれば、その値の間で直接動かせます。

最小の使用例

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に適用しているのは、スプリングのオーバーシュート中に opacity1 を超えないようにするためです。


実装例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 に渡すだけで、要素ごとに時差を設けられます。


よくある間違いと対処法

fpsuseVideoConfig() から取得しない

spring() には framefps の両方が必要です。fps をハードコードすると、コンポジションのフレームレートを変更したときにアニメーションの速度がズレます。必ず useVideoConfig() から取得してください。

const { fps } = useVideoConfig(); // 必ずこの形で

境界値のある値に overshootClamping を設定しない

プログレスバーや不透明度など、特定の範囲を超えてはいけない値には overshootClamping: true を設定するか、interpolate(){ extrapolateRight: 'clamp' } を渡してください。スプリングのオーバーシュートでプログレスバーが一瞬103%になるのはバグに見えます。

damping を極端に下げすぎる

damping6 以下になると振動サイクルが多くなり、テキストや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で波形を確認し、バウンド回数を目視で調整します。デフォルトの massstiffnessdamping5〜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ながら、映像に生命感をもたらす最も強力な手法の一つです。基本的な考え方を整理します。

  1. spring() は物理演算に基づいて from から to へ動く数値を返す
  2. interpolate() と組み合わせて、任意のCSS値へマッピングする
  3. massstiffnessdamping で動きのキャラクターを調整する
  4. パラメータ調整はSpring Editorを活用する

スプリングアニメーションをマスターすると、ほぼすべてのコンポジションで使いたくなるはずです。タイトル、ロゴ、カウンター、UI要素——すべてがリニアな動きより格段に印象的になります。


本番品質のスプリングアニメーションテンプレートは RenderComp で公開中 →

RenderCompのすべてのテンプレートは、調整済みのスプリング設定を使ってゼロから構築されています。ロゴ入場・統計カウンター・ローワーサード・その他多数——ソースコードは編集可能な状態で提供しています。

すぐに使える

1,400以上のRemotionテンプレートを一括入手

買い切り永続ライセンス。TypeScript製。今日から動画制作スピードが変わります。

RenderCompを試す →