自然な動き(Natural Motion)

スプリング・慣性・イージングを使った物理ベースのアニメーションをFramer MotionとCSS Transitionで実装・比較

Framer Motion

読み込み中...

tsx
'use client';
import { useState } from 'react';
import { motion, useAnimation } from 'framer-motion';

// スプリング — ボタンでバネのように弾んで移動
function SpringDemo() {
  const [toggled, setToggled] = useState(false);
  return (
    <div>
      <motion.div
        animate={{ x: toggled ? 160 : 0 }}
        transition={{ type: 'spring', stiffness: 200, damping: 10 }}
        style={{ width: '3.5rem', height: '3.5rem', borderRadius: '0.75rem', background: '#f43f5e' }}
      />
      <button onClick={() => setToggled((v) => !v)}>スプリング</button>
    </div>
  );
}

// 慣性 — ドラッグ後に慣性で滑るように止まる
function InertiaDemo() {
  return (
    <motion.div
      drag="x"
      dragConstraints={{ left: 0, right: 200 }}
      dragTransition={{ power: 0.3, timeConstant: 200 }}
      whileDrag={{ scale: 1.1 }}
      style={{ width: '3.5rem', height: '3.5rem', borderRadius: '0.75rem', background: '#8b5cf6', cursor: 'grab' }}
    />
  );
}

// イージング比較 — linear / easeOut / easeInOut / spring を並べて比較
const EASING_ITEMS = [
  { label: 'linear',    color: '#0ea5e9', transition: { type: 'tween', duration: 1.0, ease: 'linear' } },
  { label: 'easeOut',   color: '#10b981', transition: { type: 'tween', duration: 1.0, ease: 'easeOut' } },
  { label: 'easeInOut', color: '#f97316', transition: { type: 'tween', duration: 1.0, ease: 'easeInOut' } },
  { label: 'spring',    color: '#f43f5e', transition: { type: 'spring', stiffness: 120, damping: 12 } },
] as const;

function EasingCompareDemo() {
  const controls = useAnimation();
  const [direction, setDirection] = useState<1 | -1>(1);

  const run = async () => {
    await controls.start((i) => ({
      x: direction * 160,
      transition: EASING_ITEMS[i].transition,
    }));
    setDirection((d) => (d === 1 ? -1 : 1));
  };

  return (
    <div>
      {EASING_ITEMS.map((item, i) => (
        <motion.div
          key={item.label}
          custom={i}
          animate={controls}
          initial={{ x: 0 }}
          style={{ width: '2rem', height: '2rem', background: item.color }}
        />
      ))}
      <button onClick={run}>動かす</button>
    </div>
  );
}

jQueryで書くとこうなる

jQueryの .animate() はデフォルトで swing(ease相当)と linear のみ対応しており、スプリングのような物理ベースのアニメーションにはjQuery UIのeasingプラグインや自前のループ処理が必要でした。Reactでは Framer Motion の type: 'spring' を使うことで、stiffness(硬さ)と damping(減衰)を指定するだけで自然な物理挙動が実現できます。
js
// easing を指定した animate()(jQuery UI が必要)
$('#box').animate({ left: '200px' }, {
  duration: 600,
  easing: 'easeOutBounce',
});

// swing(jQueryデフォルトのイージング)
$('#box').animate({ left: '200px' }, 600, 'swing');

// linear
$('#box').animate({ left: '200px' }, 600, 'linear');

// jQuery単体でのスプリング再現(手動ループ)
function springAnimate(el, target, stiffness, damping) {
  let pos = 0, vel = 0;
  function step() {
    const force = (target - pos) * stiffness;
    vel = (vel + force) * damping;
    pos += vel;
    $(el).css('transform', `translateX(${pos}px)`);
    if (Math.abs(vel) > 0.1) requestAnimationFrame(step);
  }
  step();
}
💡 上記のデモは、React / Next.jsです。Next.jsの基本セットアップは 公式ドキュメントを参照してください。

実装方法の比較

項目Framer MotionCSS Transition
スプリングstiffness・dampingで細かく制御可能再現不可(cubic-bezierで近似のみ)
慣性type="inertia"でドラッグ後の滑りを自動計算再現不可
イージング指定ease・cubicBezier・springを統一APIで指定transition-timing-functionで指定
設定コスト高:framer-motionのインストールが必要低:追加ライブラリ不要
物理ベースの動きネイティブサポート非対応(近似のみ)

スプリングアニメーションのポイント

  • stiffness(バネの硬さ)が高いほど素早く動き、低いほどゆっくり動く
  • damping(減衰)が低いほど長く揺れ続け、高いほどすぐ止まる
  • mass(質量)を追加するとさらに重厚感のある動きになる
  • • CSSの cubic-bezier では静止後の揺れ(オーバーシュート)の再現が困難
  • • ドラッグ後の慣性には dragTransitionpowertimeConstant で挙動を調整できる

🤖 AIプロンプトテンプレート

Next.js + Framer Motion で物理ベースのアニメーションを実装してください。
- スプリングアニメーションでボタンクリック時に要素が弾んで移動する
- stiffness・dampingを調整して動きの硬さ・柔らかさを表現する
- linear・easeOut・springの動きをを並べて比較できるデモにする
- TypeScript / Tailwind CSS v4 を使用

⚠️ このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。