「カクつき」の正体はフレーム落ちだけではない。プロは (1) UI移動に linear を使わず役割ごとにイージングを選び分け、(2) 入る要素は減速・長め(ease-out, ~225ms)/出る要素は加速・短め(ease-in, ~195ms)と非対称に振り、(3) transform/opacity だけをアニメさせて合成層に乗せる——この3点で「安っぽさ」を消す。所要時間は実用100〜400ms、スイートスポットは200〜250msに収め、Material Design 3 のトークンをそのまま CSS 変数化するのが堅実だ。

01 — linear をやめて「減速」を効かせる

人の目は、動くものが止まる瞬間に減速を期待する。linear(等速 = cubic-bezier(0,0,1,1))は加減速がゼロで、止まる手前まで同じ速度のまま唐突に停止するため、最も機械的・安っぽく見える。UI の移動には ease-out 系を当て、終端に向けてなめらかに減速させるのが基本。等速が正しいのは無限ローディングスピナーなど一部の例外だけだ。

linear
✗ linear(等速)→ 止まり際が唐突で機械的
ease-out
✓ decelerate → 終端で静かに減速し着地する

blogEasing Functions Explained – SVGator

blogUnderstanding easing and cubic-bezier curves in CSS – Josh Collinsworth

02 — enter は減速・長め / exit は加速・短めに振り分ける

入る要素と出る要素を同じ秒数・同じカーブで動かすと、テンプレートそのままの無料感が出る。プロは非対称にする:画面に入る要素は ease-out 系で約225msかけて静かに着地させ、出る要素は ease-in 系で約195msと素早く退場させる(Material の enter225ms / exit195ms)。3つの出典がすべて一致する最重要原則で、モーダル・ドロワー・トースト全般に効く。

sym
✗ enter/exit 同カーブ・同秒 → 出入りが平板
async
✓ 入り=減速で長く / 出=加速で短く → 表情が出る

primaryTransitions – Material UI

primaryExecuting UX Animations: Duration and Motion Characteristics – NN/g

03 — width/height ではなく transform/opacity を動かす

widthheighttopleftmargin をアニメさせると、毎フレームレイアウト再計算とリペイントが走り、GPU 合成に乗らないためフレームが落ちて文字どおりカクつく。同じ「大きくなる」表現でも transform: scale() なら合成層だけで処理され、will-change: transform で明示すればサブピクセル描画も効く。動かすのは transformopacity に限定するのが根本対策だ。

✗ width/height をアニメ → layout再計算でフレーム落ち
✓ transform:scale → 合成層だけで滑らかに拡大

blogAn Interactive Guide to CSS Transitions – Josh W. Comeau

04 — 所要時間は 100〜400ms に収める

<80ms は「壊れて見える」、>500ms(モバイルは特に)は「もたつく・ラグい」と感じる。Doherty 閾値の約400msを体感の上限に据え、micro(トグル/タップ)100〜200ms・画面遷移200〜250ms・ヒーロー/モーダル300〜400ms を初期値にする。600ms以上の長尺は、面積の大きい装飾的ヒーロー演出など例外に限る。

✗ 約600ms以上 → 一拍遅れて「重い・ラグい」
✓ 200〜250ms → キビキビ即応する

primaryExecuting UX Animations: Duration and Motion Characteristics – NN/g

blogMobile App Animation Guide: Timing, Easing, and What Works

05 — hover だけは直感を逆転させる(速く入って、ゆるく出る)

画面遷移とは逆に、hover は係合時に即応・離脱時に上品が正解。マウスを乗せた瞬間(enter)は ~125ms とスナッピーに反応させ、離した後(exit)は ~450ms とゆったり戻す。CSS では基準の transition を要素側に書き、:hover 側に短い transition を上書きすると enter だけ速くなる(Comeau 式)。下のカードにカーソルを乗せて違いを確かめてほしい。

hover me
✗ enter/exit とも 450ms → 反応が鈍く感じる
hover me
✓ enter 125ms / exit 450ms → 即応かつ上品

blogAn Interactive Guide to CSS Transitions – Josh W. Comeau

06 — カーブと秒数を Material Design 3 トークンで一元管理する

カーブと秒数を毎回手打ちすると、コンポーネント間で値がばらつき品質が落ちる。Material Design 3 のトークンをそのまま CSS 変数化し、以後は var() 参照だけで方向ごとに付け替えるのが堅実だ。standard(汎用 = cubic-bezier(0.2,0,0,1))、emphasized-decelerate(enter)、emphasized-accelerate(exit)の3本を押さえる。M2 standard cubic-bezier(0.4,0,0.2,1) と M3 cubic-bezier(0.2,0,0,1) は別物なので混在禁止。

decelerate (enter)
standard (汎用)
accelerate (exit)
ポイント:decelerate は立ち上がりが急で終端がなだらか(=減速)、accelerate は逆で出だしが緩く終端が急(=加速)。曲線の形がそのまま体感に対応する。

primaryEasing and duration – Material Design 3

secondaryDesign Tokens – MDUI (Material Design 3)

実装スニペット

再利用トークン。カーブと秒数を一元化し、以後は var() 参照だけで方向ごとに付け替える。

:root{
  /* easing tokens (Material Design 3) */
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);          /* 汎用 */
  --ease-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1);  /* enter */
  --ease-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15);  /* exit */
  /* duration tokens */
  --dur-short: 150ms;   /* micro */
  --dur-base: 250ms;    /* 画面遷移スイートスポット */
  --dur-long: 400ms;    /* hero/modal */
}

enter/exit を非対称にしたモーダル。enter 側を長く減速、exit 側を短く加速させる(M2 の enter225ms / exit195ms を踏襲)。

.modal{
  opacity: 0;
  transform: translateY(8px) scale(.98);
  /* exit: 加速・短め */
  transition: opacity 195ms var(--ease-accelerate),
              transform 195ms var(--ease-accelerate);
}
.modal.is-open{
  opacity: 1;
  transform: translateY(0) scale(1);
  /* enter: 減速・長め */
  transition: opacity 225ms var(--ease-decelerate),
              transform 225ms var(--ease-decelerate);
}

hover は逆転タイミング(snappy in / relaxed out)。transform/opacity のみアニメし、:hover 側に短い transition を書くと enter が速くなる。

.card{
  will-change: transform;
  transform: translateY(0);
  /* 離脱: ゆるく */
  transition: transform 450ms var(--ease-standard),
              box-shadow 450ms var(--ease-standard);
}
.card:hover{
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgba(0,0,0,.18);
  /* 係合: 速く */
  transition: transform 125ms var(--ease-standard),
              box-shadow 125ms var(--ease-standard);
}

prefers-reduced-motion フォールバック(必須)。完全な0ではなく .01ms にすると transitionend 等の JS フックを壊さず動きだけ消せる。

@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

チェックリスト

  • UI の移動に linear を使っていない(スピナー等の等速が正しい例外を除く)
  • enter は減速・長め(ease-out, ~225ms)、exit は加速・短め(ease-in, ~195ms)に振り分けた
  • アニメ対象は transform / opacity のみ。width/height/top/left/margin を動かしていない
  • 所要時間は実用100〜400ms、スイートスポット200〜250msに収めた(80ms未満・500ms超を避けた)
  • hover は enter ~125ms / exit ~450ms と逆転させた
  • カーブと秒数は CSS 変数(M3 トークン)で一元管理。M2 と M3 のカーブを混在させていない
  • @media (prefers-reduced-motion: reduce) を実装した
  • 多階層メニューは transition-delay で doom flicker を吸収した
  • 実機(特に低速端末)で目視確認した

限界 / 出典

出典の質には差がある。最も確度が高いのは NN/g と MUI(Material 公式実装)で、cubic-bezier 値はここで実トークンとして確認できる。Material 公式(M2/M3)のページ自体は JS レンダリングで本文取得が不安定なため、数値は MUI および mdui ミラー経由で突合した二次確認である点に注意(M2 standard cubic-bezier(0.4,0,0.2,1) と M3 standard cubic-bezier(0.2,0,0,1) は別物なので混在禁止)。Josh Comeau / Collinsworth / SVGator / Appy Pie はブログで、特に hover の 125ms / 450ms や micro 時間帯は経験則であり厳密な実験値ではない。秒数(100〜400ms 中心、Doherty 約400ms)はすべて体感ガイドラインで、移動距離・面積・端末性能で適正値は動くため、最終的には実機での目視調整が前提だ。linear は原則 UI 移動では避けるが、無限ローディングスピナーなど等速が正しい例外は存在する。overshoot/bounce(制御点 Y>1)は「organic だが過剰だと安っぽい」両刃で、コーポレート系 UI では多用しないこと。値はいずれも 2026-06 時点。

blogAn Interactive Guide to CSS Transitions – Josh W. Comeau

primaryTransitions – Material UI

primaryEasing and duration – Material Design 3

secondaryDesign Tokens – MDUI (Material Design 3)

primaryExecuting UX Animations: Duration and Motion Characteristics – NN/g

blogUnderstanding easing and cubic-bezier curves in CSS – Josh Collinsworth

blogEasing Functions Explained – SVGator

blogMobile App Animation Guide: Timing, Easing, and What Works