CSSの linear() でバウンスもスプリングも ―
イージング革命の実践ガイド

cubic-bezier() では表現できなかった「跳ねる」「揺れる」動き。linear() 関数ひとつで、CSSだけの世界が一気に広がります。

なぜ cubic-bezier() だけでは足りなかったのか

CSSでアニメーションを書くとき、イージング(動きの緩急)は避けて通れません。easeease-in-out、そして自由度の高い cubic-bezier()。これらを使い分ければ、たいていの動きは作れます。

でも、「たいていの」という言葉が曲者です。

たとえば、要素がストンと落ちてポンポンと跳ねる「バウンス」。あるいは、ゴムのように行き過ぎてから戻ってくる「スプリング」。UIライブラリやモーションデザインツールでは当たり前のように使えるこれらの動き、実は cubic-bezier() では原理的に再現できません。なぜなら、3次ベジェ曲線は「行って戻る」——つまり出力値が一度上がってから下がるような曲線を描けないからです。

この制約を突破するために長い間、開発者はJavaScriptに頼ってきました。GreenSock(GSAP)や Framer Motion を使い、フレームごとに値を計算する。それが「リッチなイージング」を実現する唯一の方法だった。

そこに登場したのが、CSS の linear() 関数です。

💡 ポイント
linear() は CSS Easing Functions Level 2 で定義されたイージング関数です。キーワードの linear(等速)とは別物で、複数の数値ポイントを指定し、その間を直線で補間することで、どんな複雑な曲線でも近似できます。2023年にChrome 113で実装され、現在は主要ブラウザすべてでサポートされています。

linear() の基本 ― 最小構成で理解する

linear() の考え方はシンプルです。アニメーションの進行(0% → 100%)に対して「出力値」を数値で並べる。ブラウザはそれらの点を直線で結んで曲線を作ります。

最もシンプルな例から見てみましょう。

linear() の最小構成
/* 2点 = 等速(linear キーワードと同じ) */
animation-timing-function: linear(0, 1);

/* 3点 = 中間地点を指定 */
animation-timing-function: linear(0, 0.25, 1);

/* パーセンテージで「いつその値に達するか」を指定 */
animation-timing-function: linear(0, 0.25 75%, 1);

linear(0, 0.25, 1) の場合、3つのポイントが等間隔に配置されます。つまり、アニメーション時間の50%地点で出力が0.25になり、そこから残り50%で1.0まで一気に進む。前半はゆっくり、後半は速い——ease-in に似た動きですね。

パーセンテージを添えると、「その出力値にいつ到達するか」を細かく制御できます。linear(0, 0.25 75%, 1) なら、時間の75%を使ってやっと0.25まで進み、残り25%で一気に1.0へ。極端な加速感が生まれます。

ポイントは、数値を 0〜1の範囲外 にもできること。たとえば 1.2 を指定すれば「行き過ぎ」、-0.1 なら「戻り」が表現できます。これが、バウンスやスプリングを可能にする鍵です。

▶ Live Demo — 3種類のlinear()を比較
linear(0, 1)
linear(0, 0.25, 1)
linear(0, 0.25 75%, 1)

ボタンを押すと、3つの linear() の違いが一目でわかるはずです。同じ「0から1」でも、途中の通過点を変えるだけで、まったく異なるフィーリングが生まれる。これが linear() の基本です。

バウンスを作る ― 「1を超えて戻る」を繰り返す

実務で最もニーズの高いパターンが「バウンス」です。要素が目標位置に到達した後、何度か跳ねて停止する動き。通知バッジの出現、ドロップダウンの展開、カードの追加——こうした場面で使うと、UIにぐっと生命感が出ます。

バウンスの仕組みは単純です。出力値が 1(目標値)に到達した後、少し戻って、また1に達して、さらに小さく戻って……を繰り返す。linear() でこれを書くと、こうなります。

バウンスイージング
.bounce-element {
  animation: bounceIn 800ms linear(
    0, 0.004, 0.016, 0.035, 0.063 9.1%,
    0.141, 0.25, 0.391, 0.563, 0.765, 1,
    0.891, 0.813 45.5%, 0.785, 0.766,
    0.754, 0.75, 0.754, 0.766, 0.785,
    0.813 63.6%, 0.891, 1 72.7%,
    0.973, 0.953, 0.941, 0.938,
    0.941, 0.953, 0.973, 1,
    0.988, 0.984, 0.988, 1
  ) forwards;
}

@keyframes bounceIn {
  from { transform: translateY(-100px); }
  to   { transform: translateY(0); }
}

数値の羅列に圧倒されるかもしれませんが、安心してください。これは手で書くものではありません。Jake ArchibaldとAdam Argyleが公開している Linear Easing Generator にプリセットとして「Bounce」が用意されていて、ワンクリックで生成できます。

▶ Live Demo — バウンスで落ちてくるボール

JavaScriptは一行も使っていません(再生ボタンのクリックイベントを除けば)。キーフレーム自体はただの translateY(0)translateY(120px) という単純な直線移動。それが linear() のイージングだけで、まるで物理法則に従っているかのように跳ねる。これがこの関数の真価です。

📌 ポイント
バウンスの「跳ね具合」を調整したい場合は、Linear Easing Generator のスライダーでポイント数を増減させましょう。ポイントが多いほど滑らかになりますが、CSSのファイルサイズも増えます。実用上は20〜40ポイントで十分滑らかに見えます。

スプリングを作る ― 物理シミュレーション感のあるUI

バウンスが「跳ねて止まる」動きなら、スプリングは「行き過ぎて揺れ戻る」動き。モーダルの出現、トグルスイッチの切り替え、リスト要素の追加——弾力のある動きが欲しいとき、スプリングは最適です。

スプリングでは出力値が 1 を超えて 1.11.2 まで「行き過ぎ」、そこから 0.95 まで戻り、また 1.02 まで行って……と振動しながら収束します。

スプリングイージング
.spring-element {
  transition: transform 600ms linear(
    0, 0.009, 0.035, 0.078, 0.141 13.6%,
    0.32, 0.557 27.3%, 0.801, 0.941,
    1.029 40.9%, 1.087, 1.119,
    1.126 52.3%, 1.107, 1.067,
    1.022 63.6%, 0.986, 0.966,
    0.958 75%, 0.966, 0.982,
    0.998 86.4%, 1.006, 1.01,
    1.008 95.5%, 1
  );
}
▶ Live Demo — スプリングで飛び出すカード
A
B
C
D

各カードに transition-delay を少しずつずらすだけで、順番に弾むように出現するスタッガードアニメーションが完成します。以前ならGSAPの stagger オプションが必要だった表現が、CSSだけで書ける。しかも、transition なので prefers-reduced-motion との組み合わせも簡単です。

▶ Live Demo — ease-out vs スプリング 比較
ease-out
ease
spring
spring

同じ距離、同じ時間でも、スプリングのほうが「生きている」感じがしませんか。ease-out は減速して止まるだけですが、スプリングは少しだけ行き過ぎてから戻る。この微小な「揺らぎ」が、人間の目に自然な動きとして映るのです。

実践パターン集 ― 現場で使える5つのデモ

ここからは、現場で実際に使えるパターンをライブデモ付きで紹介します。

1. 通知バッジのポップイン

▶ Live Demo — バッジのポップイン
🔔
3
✉️
12

2. エラスティック(弾性)テキスト

▶ Live Demo — 弾むように現れるテキスト
linear()

3. トグルスイッチのスプリング

▶ Live Demo — クリックでスプリング切り替え

4. フローティングアクションボタン(FAB)の展開

▶ Live Demo — FABメニューのスプリング展開
📷
📎
✏️
+

5. カードのホバーエフェクト

▶ Live Demo — ホバーでスプリングする影と浮遊感
🎨
Design
Speed
🧩
Component

カードをホバーしてみてください。ease では味わえない「ふわっと浮いてトンと着地する」感覚が伝わるはずです。

アクセシビリティと linear() ― 忘れてはいけないこと

リッチなアニメーションを追求すると、つい忘れがちなのが prefers-reduced-motion への対応です。前庭障害(めまいや動揺病を起こしやすい状態)のあるユーザーにとって、バウンスやスプリングは文字通り「気持ち悪い」動きになり得ます。

✅ prefers-reduced-motion への対応
.card {
  transition: transform 500ms linear(/* スプリング値 */);
}

@media (prefers-reduced-motion: reduce) {
  .card {
    transition: transform 200ms ease;
  }
}

動きの軽減設定が有効なときは、linear()ease に差し替え、durationも短くする。たったこれだけで、アニメーションが苦手なユーザーにも配慮できます。

⚠️ 注意
linear() のブラウザサポートは Chrome 113+、Firefox 112+、Safari 17.2+ です。2024年以降のモダンブラウザであればまず問題ありませんが、古い環境が残っている案件では @supports でフォールバックを用意しましょう。cubic-bezier()ease-out に差し替えるだけで十分です。

まとめ

  • linear() は複数の数値ポイントを直線で結ぶことで、どんなイージング曲線でも近似できるCSS関数である
  • cubic-bezier() では不可能だったバウンス・スプリング・弾性効果が、CSSだけで実現可能になった
  • 値の手書きは不要。Jake Archibaldの Linear Easing Generator でプリセットから生成できる
  • transition と組み合わせれば、ホバーやクラスの付け替えだけでスプリング動作が得られる
  • transition-delay をずらすことで、スタッガード(連鎖)アニメーションもCSS完結で書ける
  • prefers-reduced-motion 対応を必ずセットで考える。バウンスは前庭障害のあるユーザーに負荷がかかる
  • Chrome 113+ / Firefox 112+ / Safari 17.2+ でサポート済み。古い環境には ease-out でフォールバックする