Framer Motionは、Reactでアニメーションを書くためのオープンソースライブラリです。2024年11月12日に開発元のFramer社から独立し、名称が「Motion」に変わりました。npmのパッケージは motion、Reactから読み込むパスは motion/react です。書き方は旧 framer-motion と同じで、2026年9月時点の最新版は両パッケージとも13.4.4です。
この記事では、改称後のパッケージの選び方と motion コンポーネントの基本を押さえます。そのうえで、「transitionを書かなかったときに何が起きるか」を、v13.4.4のソースコードに基づいて説明します。easing(イージング)とspringの設定値、reducedMotionの初期値、バンドルサイズも扱います。
まとめ:Framer Motion(Motion)の要点
- Framer Motionは2024年11月にMotionへ改称した。新規導入は
npm install motionで入れ、import { motion } from "motion/react"と書く。既存のframer-motionも同じ版番号で更新が続いている。 - transitionを書かない場合、x・rotate・scaleなどの変形はspring、opacityや色は0.3秒のtween(曲線は
cubic-bezier(0.25, 0.1, 0.35, 1))で動く。durationだけを書くと、変形もspringではなくeaseOutのtweenになる。 easeに文字列で渡せる名前は11種類。3次ベジェの配列、steps()、自作関数も使える。"easeOutCubic"のような未定義の名前は、開発ビルドではエラーになる。- springは、
stiffness・damping・massの物理指定か、visualDuration・bounceの時間指定で組む。両方を書くと物理指定が優先される。v13.4.4では、visualDurationだけを書いても時間指定として扱われない。 reducedMotionの初期値は"never"で、OSの「視差効果を減らす」設定を尊重しない。MotionConfig reducedMotion="user"を明示する必要がある。
Framer MotionからMotionへの改称とパッケージの関係
作者のMatt Perry氏は2024年11月12日、公式ブログで「Framer Motion is now independent, introducing Motion」と発表しました。同時に、それまで別ライブラリだったMotion One(素のJavaScript向け)と統合しています。以後はReactに加えて素のJavaScriptからも使えるライブラリになり、2025年3月19日にはVue版も公開されました。
Framer(ノーコードのWebサイト制作ツール)とは別の製品です。Framer Motionは、もともとFramer社の中で開発されていました。検索で「Framer 特徴」「フレーマーとは」と調べてこの記事に来た場合、探しているのはサイト制作ツールのほうかもしれません。
framer-motionとmotionの選択基準
npmでは motion パッケージが framer-motion に依存しており、版番号も揃えて公開されています(2026年9月時点でどちらも13.4.4)。どちらを入れても、React向けの中身は同じです。
| 項目 | motion | framer-motion |
|---|---|---|
| 読み込みパス | motion/react | framer-motion |
| 最新版(2026年9月) | 13.4.4 | 13.4.4 |
| 週間DL数(9月18〜24日) | 約2,004万 | 約4,331万 |
| React | 18.2以上 | 18.2以上 |
| ライセンス | MIT | MIT |
週間DL数はnpmのDownloads API(api.npmjs.org/downloads/point/last-week)で取得した値です。framer-motion のダウンロード数には、motion の依存として取得される分も含まれます。この数字は、旧パッケージ名が主流だという根拠にはなりません。新規プロジェクトでは、公式ドキュメントの表記に合わせて motion を選ぶのが無難です。同等バージョンでパッケージ名だけを移行する場合は、パッケージを入れ替えてimport文を書き換えます。古いメジャーバージョンから更新する場合は、公式アップグレードガイドで各版の破壊的変更も確認してください。
npm uninstall framer-motion
npm install motion
// 変更前
import { motion } from "framer-motion"
// 変更後
import { motion } from "motion/react"
v12・v13で変わった点
メジャー版が上がっても、Reactで使うときの破壊的変更は小さく抑えられています。
| 版 | 公開日 | React利用者への影響 |
|---|---|---|
| 11.11.12 | 2024-11-12 | motion パッケージ追加(改称) |
| 12.0.0 | 2025-01-20 | React向けAPIの破壊的変更なし |
| 13.0.0 | 2026-08-05 | @emotion/is-prop-valid の自動読み込み廃止 |
| 13.4.0 | 2026-09-14 | AnimateView(React 19.3のViewTransition)追加 |
v12で変わったのは素のJavaScript向けAPIです。press・hover・inView のコールバックは、第1引数に対象要素を受け取るようになりました。v13で影響を受けるのは、主にstyled-componentsやEmotionで motion コンポーネントにスタイルを当てている構成です。更新後に、これまで除外されていた独自のpropsがDOMに出力されるようになった場合、公式ガイドは2つの対処を挙げています。1つは <MotionConfig isValidProp={isPropValid}> で @emotion/is-prop-valid を明示的に渡す方法です。もう1つは motion.create(StyledDiv) のように包む順序を逆にし、props転送をスタイリングライブラリ側に任せる方法です。CSS-in-JSとの併用については「Tailwind CSSとEmotionの違い|v4での使い分けと共存」も参考になります。
motionコンポーネントの使い方:initial・animate・exit
motion.div や motion.button は、普通のHTML要素にアニメーション用のpropsを足したコンポーネントです。initial に開始状態、animate に目標の状態を書くと、マウント時とpropsが変わったときにその差分を補間します。
import { motion } from "motion/react"
export function FadeIn() {
return (
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease: "easeOut" }}
>
Hello, Motion
</motion.div>
)
}
ホバーやタップの反応は whileHover・whileTap に書きます。要素が消えるときのアニメーションは、AnimatePresence で包んだうえで exit に書きます。AnimatePresence がないと、Reactが要素を即座に取り除くため、exit は再生されません。
import { motion, AnimatePresence } from "motion/react"
import { useState } from "react"
export function Toggle() {
const [open, setOpen] = useState(false)
return (
<>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setOpen(!open)}
>
開閉
</motion.button>
<AnimatePresence>
{open && (
<motion.div key="panel" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>開閉するパネル</motion.div>
)}
</AnimatePresence>
</>
)
}
Next.js App Routerでの読み込み方
公式のインストール手順によると、App Routerでは2通りの書き方があります。1つは、読み込むファイルの先頭に "use client" を書いてクライアントコンポーネントにする方法です。もう1つは、Server Componentのまま import * as motion from "motion/react-client" で読み込む方法です。後者のほうが、クライアントへ送るJavaScriptを減らせます。
import * as motion from "motion/react-client"
export default function Page() {
return <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
}
initial の値はサーバー側のHTMLにも出力されます。ランダムな値や window のサイズから initial を計算すると、サーバーとクライアントで値がずれ、ハイドレーションの不一致が起きます。原因の切り分け方は「ハイドレーションエラー(Hydration Error)とは?原因とReact / Next.jsでの対処法を解説」で扱っています。
transition省略時の既定値:プロパティ別のアニメーション設定
公式ドキュメントが説明しているのは、「xやscaleのような物理的な値はspring、opacityや色はtween」という大枠までです。具体的な数値は、v13.4.4のソースコード(default-transitions.ts)を読むと分かります。アニメーションさせる値の種類ごとに、次の設定が選ばれます。アニメーションさせる値の種類ごとに次の設定が選ばれます。
| 対象の値 | 種類 | 既定の設定 |
|---|---|---|
| x・y・rotate など(scale以外の変形) | spring | stiffness 500 / damping 25 |
| scale・scaleX・scaleY | spring | stiffness 550 / damping 30 |
| opacity・色・width など | tween | 0.3秒 / [0.25, 0.1, 0.35, 1] |
| キーフレームが3つ以上 | tween | 0.8秒 / easeOut |
scaleの目標値が0のときは、dampingが 2 × √550(約46.9)の臨界減衰になり、行き過ぎずに縮みきります。opacityなどに使われる曲線について、ソースには「ブラウザ既定のeaseを少し浅くしたもの」というコメントがあります。CSSの ease は cubic-bezier(0.25, 0.1, 0.25, 1) です。
duration単独指定によるtweenへの切り替え
実装でつまずきやすいのは、既定値の切り替え条件です。transition に delay・repeat・when などの順序制御のキーだけが入っている場合、上の表の既定値がそのまま使われます。それ以外のキーが1つでもあると、表の既定値は使われません。そのうえで type も ease もなければ、easeOutのtweenになります。
そのため、x に transition={{ duration: 0.5 }} とだけ書くと、springの揺れがなくなり、0.5秒のeaseOutで止まります。「durationを足したら動きが硬くなった」ときは、springからtweenへの切り替えを確認してください。時間を指定しつつspringの質感を残したい場合は、type: "spring" と visualDuration を組み合わせます(後述)。
easing(ease)の指定方法:名前・ベジェ・steps・関数
tweenの加速・減速の曲線は transition の ease で指定します。渡せるのは、組み込みの名前、4つの数値の配列(3次ベジェ)、0〜1の進捗を受け取って値を返す関数のいずれかです。曲線そのものの仕組みは「ベジェ曲線とは?仕組みをわかりやすく解説|数式・Illustratorでの描き方・スプラインとの違い」を参照してください。
組み込みのeasing名11種と曲線の値
名前で指定できるのは、ソース(easing/utils/map.ts)の easingLookup に登録された11種類です。下の表の「50%時点」は、[email protected] が公開している関数に進捗0.5を渡し、小数第3位に丸めた出力で、Node.jsで実行して確かめました。
| 名前 | 定義 | 50%時点 |
|---|---|---|
| linear | そのまま | 0.500 |
| easeIn | [0.42, 0, 1, 1] | 0.315 |
| easeOut | [0, 0, 0.58, 1] | 0.685 |
| easeInOut | [0.42, 0, 0.58, 1] | 0.500 |
| circIn / circOut / circInOut | 円弧型 | circOut 0.866 |
| backIn / backOut / backInOut | backOut [0.33, 1.53, 0.69, 0.99] | backOut 1.067 |
| anticipate | 一度戻ってから進む | 0.500 |
backOutは最大で約1.084まで行き過ぎてから戻ります。anticipateは、開始直後に約-0.042まで逆方向へ振れます。どちらも、ボタンやトーストのように「弾み」が意味を持つ部品に向いています。一方、スクロール位置や数値の表示に使うと、目標を超えた値が一瞬表示されてしまいます。
CSSでおなじみの "ease" や、他のライブラリにある "easeOutCubic" はこの一覧にありません。TypeScriptでは型エラー(TS2322)になり、JavaScriptでも開発ビルドでは「Invalid easing type」というエラーが投げられます。本番ビルドでは、このチェックが外れます。13.4.2では、キーフレーム補間が未知の名前でクラッシュしないようにeaseInOutへフォールバックする修正が入りました(CHANGELOGの記載は「Unrecognised easing names no longer throw」)。
ベジェ配列・キーフレームごとのease・steps・自作関数
import { motion, cubicBezier, steps } from "motion/react"
// 3次ベジェを配列で直接指定
<motion.div animate={{ x: 200 }} transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }} />
// キーフレームの区間ごとに別のeaseを当てる(区間数=キーフレーム数-1)
<motion.div
animate={{ x: [0, 120, 80] }}
transition={{ duration: 1, times: [0, 0.6, 1], ease: ["easeIn", "backOut"] }}
/>
// 4段階でカクカク進める
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} style={{ width: 80, height: 80, backgroundColor: "tomato" }} transition={{ duration: 1, ease: steps(4) }} />
// 関数を渡す(cubicBezierで作った関数も同じ扱い)
<motion.div animate={{ x: 100 }} transition={{ duration: 0.5, ease: cubicBezier(0.65, 0, 0.35, 1) }} />
<motion.div animate={{ x: 100 }} transition={{ duration: 0.5, ease: (t: number) => t * t }} />
ease にeasingの配列を渡すと、キーフレームの各区間に1つずつ割り当てられます。times は各キーフレームの位置を0〜1で指定するもので、上の例では60%の時点で120に達します。steps(4) は進捗を4段階に丸めます。第2引数の既定は "end" で、各段の終わりで次の値へ切り替わります。
springの設定:物理パラメータと時間指定
type: "spring" を指定すると、補間ではなくばねの物理シミュレーションで動きます。組み方は2通りあります。ソース(generators/spring.ts)の getSpringOptions では、stiffness・damping・mass のどれかがあると物理指定として扱われ、duration・bounce は無視されます。
| 指定方法 | キー | 省略時・注意 |
|---|---|---|
| 物理指定 | stiffness / damping / mass | 100 / 10 / 1 |
| 時間指定 | visualDuration + bounce | bounceを併記して使う |
| 時間指定(旧来) | duration / bounce | 0.8秒 / 0.3 |
// 見た目上0.4秒で目標に届き、少しだけ弾む
<motion.div animate={{ x: 100 }} transition={{ type: "spring", visualDuration: 0.4, bounce: 0.25 }} />
// 物理パラメータで直接指定
<motion.div animate={{ x: 100 }} transition={{ type: "spring", stiffness: 300, damping: 20, mass: 1 }} />
visualDuration は、揺れが収まるまでではなく、目標に見かけ上届くまでの秒数です。ソースでは stiffness =(2π ÷(visualDuration × 1.2))²、damping = 2 × clamp(0.05, 1, 1 − bounce) × √stiffness に換算しています。clampは値を0.05以上1以下に制限する処理です。bounceを0にすると臨界減衰になり、行き過ぎません。
旧来の duration 指定でbounceを省略した場合、公式ドキュメントの spring のページは既定値を0.25と記載しています。一方、v13.4.4のソースの既定値は0.3です。spring 関数に0.8秒のdurationだけを渡して実行すると、出力は bounce: 0.3 を指定した場合と一致しました。動きを揃えたい場合は、bounceを省略せずに書いてください。
visualDurationの時間指定とbounce併記の条件
v13.4.4の getSpringOptions は、duration か bounce が書かれているときだけ時間指定と判定します。visualDuration だけを書いた場合は判定から漏れ、物理指定の既定値(stiffness 100・damping 10)で動きます。motion パッケージが公開している spring 関数をNode.jsで実行したところ、{ visualDuration: 0.4 } の出力は何も指定しない場合と一致し、最大で目標の116%まで行き過ぎました。一方、{ visualDuration: 0.4, bounce: 0 } では行き過ぎはなく、0.4秒の時点で目標の96.7%に届きました。時間で合わせたいときは、bounce を必ず併記してください。
type: "spring" だけを書いた場合も同じ既定値(100・10)になります。x・yの既定値(500・25)と比べると固有振動数は約2.2分の1(√100 と √500 の比)です。何も書かないときより遅く、大きく揺れる動きになります。
delay・repeat・staggerによるタイミング制御
開始を遅らせるには delay(秒)を使います。repeat には回数か Infinity を指定し、repeatType で "loop"(既定)・"reverse"・"mirror" を選びます。値ごとに別のtransitionを当てたいときは、値の名前をキーにして入れ子にします。
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} style={{ width: 80, height: 80, backgroundColor: "tomato" }} transition={{ delay: 0.3, duration: 0.5 }} />
<motion.div animate={{ rotate: 180 }} transition={{ repeat: Infinity, repeatType: "reverse", duration: 2 }} />
<motion.div
animate={{ x: 100, opacity: 1 }}
transition={{ x: { type: "spring", bounce: 0.3 }, opacity: { duration: 0.2, delay: 0.1 } }}
/>
リストの項目を順番に表示するときは、親と子に variants を持たせます。v13の型定義では staggerChildren に @deprecated が付いており、代わりに delayChildren: stagger(間隔) と書くよう案内されています。stagger の from オプションを使うと、最後の要素や中央から順に動かせます。
import { motion, stagger } from "motion/react"
const list = { hidden: {}, show: { transition: { delayChildren: stagger(0.08) } } }
const item = { hidden: { opacity: 0, y: 8 }, show: { opacity: 1, y: 0 } }
export function List({ items }: { items: string[] }) {
return (
<motion.ul variants={list} initial="hidden" animate="show">
{items.map((t) => (
<motion.li key={t} variants={item}>{t}</motion.li>
))}
</motion.ul>
)
}
ドラッグとスクロール連動
drag に "x" か "y" を渡すと、その軸だけでドラッグできます。dragConstraints には移動範囲を数値で渡すか、親要素のrefを渡します。画面内に入ったときに再生するには whileInView を使います。viewport の once: true で再生を1回に限り、amount で要素の何割が見えたら発火するかを決めます。
<motion.div drag="x" dragConstraints={{ left: -100, right: 100 }} />
<motion.section
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, amount: 0.3 }}
/>
スクロール量に比例して動かしたい場合は、useScroll で進捗のモーションバリューを受け取り、useTransform で別の値に変換します。モーションバリューはReactの再レンダリングを経由せずに値を更新するため、スクロールのように毎フレーム変わる入力に向いています。
アクセシビリティとバンドルサイズの注意点
reducedMotionの初期値neverとuserの指定
Motionは、利用者がOSで「視差効果を減らす」(macOS・iOS)のような動きを減らす設定を選んでいても、初期状態ではそれを尊重しません。MotionConfigContext の初期値が reducedMotion: "never" だからです。公式ドキュメントのアクセシビリティのページは、アプリ全体を MotionConfig で包み、reducedMotion="user" を指定する方法を案内しています。
import { MotionConfig, useReducedMotion, motion } from "motion/react"
export function App({ children }: { children: React.ReactNode }) {
return <MotionConfig reducedMotion="user">{children}</MotionConfig>
}
// 個別に出し分けたい場合
export function Hero() {
const reduce = useReducedMotion()
return <motion.div animate={{ x: reduce ? 0 : 100, opacity: 1 }} />
}
"user" にすると、OSの動きを減らす設定が有効な利用者に対して、変形とレイアウトのアニメーションが無効になり、opacityや背景色のアニメーションは残ります。自動再生の動画や視差スクロールのように、変形以外で動いている要素は useReducedMotion の戻り値で個別に止めます。
LazyMotionとmによる初期ロードの削減
公式の「Reduce bundle size」ページの掲載値によると、motion コンポーネントは宣言的なprops APIを持つため、tree shakingしても34KBより小さくなりません。m コンポーネントと LazyMotion に置き換え、機能を後から非同期で読み込む構成にすると、初回描画に必要な分は4.6KB弱になります。読み込む機能の目安は、domAnimation(アニメーション・variants・exit・tap/hover/focus)で+15KB、domMax(ドラッグとレイアウトアニメーションを追加)で+25KBです。
features={domAnimation} のように同期でimportすると、機能分も初期バンドルに入ります。初回描画を軽くしたいときは、機能を別ファイルに切り出し、動的importで渡します。
// features.ts
import { domAnimation } from "motion/react"
export default domAnimation
// App.tsx
import { LazyMotion } from "motion/react"
import * as m from "motion/react-m"
const loadFeatures = () => import("./features").then((res) => res.default)
export function LazyApp() {
return (
<LazyMotion features={loadFeatures} strict>
<m.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>LazyMotionの表示例</m.div>
</LazyMotion>
)
}
strict を付けると、LazyMotion の内側で通常の motion を使ったときにエラーになります。そのため、置き換え漏れで34KBが戻ってくる事態を防げます。
CSSアニメーションや他ライブラリとの使い分け
Motionが強いのは、Reactの状態と連動するアニメーションです。マウントとアンマウント(AnimatePresence)、レイアウト変化の補間、ドラッグ、途中で目標が変わっても速度を引き継ぐspringなどが該当します。逆に、ホバー時の色の変化や、読み込み中の点滅のように状態を持たない装飾だけなら、CSSの transition と @keyframes で足ります。そうした用途のためだけに motion を読み込み、34KBを増やすのは割に合いません。
| 用途 | 第一候補 |
|---|---|
| ホバー・フォーカスの色や影 | CSS transition |
| 表示・非表示、リストの並べ替え | Motion(AnimatePresence・layout) |
| ページ遷移 | View Transitions API |
| hooksだけでspringを扱う | react-spring |
| タイムライン中心の演出・非React | GSAP・Anime.js |
ページ遷移は、ブラウザ標準の機能でも実装できます(「View Transitions APIの対応ブラウザと実装手順|クロスドキュメント遷移まで」)。React 19.3以降であれば、Motion 13.4で追加された AnimateView が、ReactのViewTransitionの上に組まれています。hooks中心の設計が好みなら「react-springの使い方|useSpring・useTrail・useChainをv10系で実装する」、タイムラインで演出を組むなら「Anime.js v4の変更点と使い方|v3からの移行手順とGSAPとの違い」と比べてください。
よくある質問
Framer MotionとMotionは別のライブラリですか?
同じライブラリです。2024年11月12日にFramer社から独立し、名称がMotionに変わりました。Reactでは motion/react から読み込み、APIは framer-motion と共通です。
Framer Motion(Motion)は無料で商用利用できますか?
できます。motion と framer-motion はどちらもMITライセンスです。有料のMotion+は追加のコンポーネントや例を提供する別商品で、本体の利用には必要ありません。
framer motionのeasingには何を指定できますか?
linear・easeIn・easeOut・easeInOut・circIn・circOut・circInOut・backIn・backOut・backInOut・anticipate の11種類の名前と、[0.22, 1, 0.36, 1] のような3次ベジェの配列、steps()、自作の関数です。CSSの "ease" は名前として使えません。
framer motionでdelayを設定するには?
transition={{ delay: 0.3 }} のように秒で指定します。delayだけならx・yのspringなどの既定値は保たれます。子要素を順番に遅らせるときは、親のvariantsに delayChildren: stagger(0.08) を書きます。
Next.jsのApp Routerで使えますか?
使えます。ファイルの先頭に "use client" を書いて motion/react から読み込むか、Server Componentのまま import * as motion from "motion/react-client" と書きます。