「カクつき」の正体はフレーム落ちだけではない。プロは (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 系を当て、終端に向けてなめらかに減速させるのが基本。等速が正しいのは無限ローディングスピナーなど一部の例外だけだ。
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つの出典がすべて一致する最重要原則で、モーダル・ドロワー・トースト全般に効く。
primaryTransitions – Material UI
primaryExecuting UX Animations: Duration and Motion Characteristics – NN/g
03 — width/height ではなく transform/opacity を動かす
width・height・top・left・margin をアニメさせると、毎フレームレイアウト再計算とリペイントが走り、GPU 合成に乗らないためフレームが落ちて文字どおりカクつく。同じ「大きくなる」表現でも transform: scale() なら合成層だけで処理され、will-change: transform で明示すればサブピクセル描画も効く。動かすのは transform と opacity に限定するのが根本対策だ。
blogAn Interactive Guide to CSS Transitions – Josh W. Comeau
04 — 所要時間は 100〜400ms に収める
<80ms は「壊れて見える」、>500ms(モバイルは特に)は「もたつく・ラグい」と感じる。Doherty 閾値の約400msを体感の上限に据え、micro(トグル/タップ)100〜200ms・画面遷移200〜250ms・ヒーロー/モーダル300〜400ms を初期値にする。600ms以上の長尺は、面積の大きい装飾的ヒーロー演出など例外に限る。
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 式)。下のカードにカーソルを乗せて違いを確かめてほしい。
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) は別物なので混在禁止。
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