CSS cubic-bezier() の数学 — xを[0,1]に制限する理由と逆関数の数値解法
GDESIGN
デザイン(更新: 2026-10-14)9 分で読める

CSS cubic-bezier() の数学 — xを[0,1]に制限する理由と逆関数の数値解法

CSS アニメーションの transition-timing-function に書く cubic-bezier(0.25, 0.1, 0.25, 1.0) — この 4 つの数値が何を意味するか、正確に理解していますか? Bézier 曲線を「描画」に使う SVG とは異なり、CSS では Bézier を「時間→進行度のマッピング関数」として使います。そのために P₀=(0,0)、P₃=(1,1) を固定し、x₁/x₂ を [0,1] に制限するという制約があります。本記事では W3C CSS Easing Functions Level 2 を一次資料に、この制約の数学的根拠と、逆関数を求めるNewton-Raphson法・二分法を整理します。仕様はブラウザ内部の具体的な求解アルゴリズムまでは規定していません。

#CSS#cubic-bezier#Bézier曲線#Newton-Raphson#数学

CSS における Bézier 曲線の役割 — 描画ではなく「関数」

SVG の <path> では Bézier 曲線は 2D 平面上の形状を描きます (参考: SVG path の Bézier と楕円弧)。一方 CSS では全く異なる使い方をします:

  • 横軸 x: 正規化された経過時間 (0 = 開始、1 = 終了)
  • 縦軸 y: 正規化されたプロパティの進行度 (0 = 開始値、1 = 終了値)

つまり cubic-bezier(x1, y1, x2, y2) は時間→進行度の 1 次元関数 f(x) を定義しています。「曲線を描く」のではなく「アニメーションの緩急をコントロールする」ために Bézier を使っているのです。

W3C CSS Easing Functions Level 2の公開草案 §2.3 では、P₀ = (0, 0)、P₃ = (1, 1) を固定し、ユーザーが指定するのは P₁ = (x₁, y₁) と P₂ = (x₂, y₂) のみです。本記事の数値解法は入力進行度 x ∈ [0,1] を対象とします。CSS仕様では区間外の入力も定義され、端点側の制御点に基づく直線で外挿します。ここで使う曲線パラメータ t は、経過時間そのものではありません。

3 次 Bézier のパラメトリック方程式と Bernstein 基底

3 次 Bézier 曲線は、パラメータ t ∈ [0, 1] を変化させたとき、4 つの制御点 P₀, P₁, P₂, P₃ から以下の式で点 B(t) を生成します:

B(t) = (1-t)³ P₀ + 3(1-t)² t P₁ + 3(1-t) t² P₂ + t³ P₃

この式の各係数 (1-t)³, 3(1-t)²t, 3(1-t)t², t³ は Bernstein 基底多項式 B_{i,3}(t) です。二項展開から、以下の性質を確認できます:

  • 非負性: すべての i について B_{i,n}(t) ≥ 0 (t ∈ [0,1])
  • 1の分割 (partition of unity): Σ B_{i,n}(t) = 1 (重みの合計が常に 1)
  • 対称性: B_{i,n}(t) = B_{n-i,n}(1-t)

この「非負で合計 1」の性質は、曲線上の点が制御点の凸結合 (convex combination) であることを保証します。つまり曲線は4制御点全体の凸包からはみ出しません。CSSではP₀.x=0、P₃.x=1、P₁.xとP₂.xも0〜1なので、x(t)も0〜1の範囲に保たれます。

なぜ x₁ と x₂ を [0, 1] に制限するのか — 単調性の保証

CSS の cubic-bezier() で P₀ = (0,0), P₃ = (1,1) を固定すると、x 座標の式は:

x(t) = 3(1-t)² t · x₁ + 3(1-t) t² · x₂ + t³

この関数が「時間」を表すには、x(t) が t について単調増加でなければなりません。もし x(t) が途中で減少すると「時間が戻る」ことになり、同じ時刻 x に対して複数の t が対応してしまいます (関数として成立しない)。

x(t) の単調増加条件は、導関数 x'(t) ≥ 0 (全 t ∈ [0,1]) です:

x'(t) = 3(1-t)² x₁ + 6(1-t)t(x₂ - x₁) + 3t²(1 - x₂)
       = 3[(1-t)² x₁ + 2(1-t)t(x₂ - x₁) + t²(1 - x₂)]

この 2 次式が [0,1] 上で非負であるための十分条件は x₁ ∈ [0,1] かつ x₂ ∈ [0,1] です。x₁ ≤ x₂ という追加条件は不要です。以下は式からの導出です。tを固定すると x'(t)/3 は x₁, x₂ の一次式なので、単位正方形の四隅の値の凸結合として表せます。

(x₁, x₂) = (0, 0): x'(t)/3 = t²
(x₁, x₂) = (0, 1): x'(t)/3 = 2t(1-t)
(x₁, x₂) = (1, 0): x'(t)/3 = (1-2t)²
(x₁, x₂) = (1, 1): x'(t)/3 = (1-t)²

四隅の値はいずれも非負です。さらにx(t)は定数多項式ではないため、導関数が区間全体で0になることはなく、x(t)は厳密に増加します。ただし各点で x'(t) > 0 とは限りません。x₁=1, x₂=0 では t=0.5 で導関数が0になりますが、逆関数は一意です。

一方 y₁ と y₂ には制限がありません。y は「進行度」であり、CSS では cubic-bezier(0.68, -0.55, 0.27, 1.55) のように y が [0,1] を超える値も許されます。これが「行き過ぎ (overshoot)」アニメーションを表現し、弾むような動きになります。

ブラウザの課題 — x から t を求める逆問題

アニメーションの各フレームで、ブラウザは「現在の経過時間 x が与えられたとき、進行度 y を求める」必要があります。しかし Bézier 曲線の式が直接与えるのは t → (x, y) です。xをそのままtへ代入しても、一般には正しい進行度になりません。

そこで 2 段階の計算が必要です:

  1. 逆関数: 与えられた x に対して x(t) = x を満たす t を求める
  2. 順関数: 求めた t を y(t) に代入して進行度を得る

ステップ 1 は 3 次方程式の求根問題です。解析解を使う方法もありますが、実装では精度、計算量、境界での安定性を考慮して反復法を選べます。CSS仕様は特定の求解アルゴリズムを要求していません。

Newton-Raphson 法と二分法 — 逆関数を数値的に解く

代表的な数値解法が Newton-Raphson 法 (ニュートン法) です。求めたいのは x(t) - x_target = 0 の根で、反復式は:

t_{n+1} = t_n - (x(t_n) - x_target) / x'(t_n)

ここで x'(t) は先述の導関数です。端点付近だけでなく、x₁=1, x₂=0, t=0.5 のように区間内で導関数が0になる場合もあります。ゼロ除算や区間外への逸脱を避けるため、残差・導関数・推定値・反復上限を確認し、Newton法を安全に使えない場合は二分法へ切り替えます。

二分法は各反復で t の候補区間を半分にします。Newton法より反復回数は増えやすい一方、x(t)の単調性を利用して区間を保てます。NanTooのcubic-bezierエディタは、区間内のプレビュー値に40回の二分法を使い、入力進行度0と1は直接返します。理想的な実数計算では40回後の候補区間幅は2⁻⁴⁰ですが、浮動小数点の丸め誤差を含むyの精度まで保証する値ではありません。

CSSの仕様とツールの制限は別です。 本ツールは操作範囲としてx₁/x₂を0〜1、y₁/y₂を−1〜2へ制限し、小数第2位へ丸めます。入力進行度も0〜1へ制限するため、CSS仕様の区間外外挿を検証する汎用評価器ではありません。

CSS 標準キーワードと cubic-bezier() の対応

W3C CSS Easing Functions Level 2では、ease系の4キーワードがcubic-bezier()に対応します。linear は別の線形イージングキーワードですが、下表の曲線と同じ y=x を与えます:

表が収まらない場合は、左右にスクロールできます。

キーワードcubic-bezier()特徴
ease(0.25, 0.1, 0.25, 1.0)ゆっくり始まり、急加速、ゆっくり終わる
ease-in(0.42, 0, 1.0, 1.0)ゆっくり始まり、加速して終わる
ease-out(0, 0, 0.58, 1.0)素早く始まり、減速して終わる
ease-in-out(0.42, 0, 0.58, 1.0)ゆっくり始まり、ゆっくり終わる (対称)
linear(0, 0, 1.0, 1.0)等速 (制御点が対角線上)

transition-timing-function の初期値は ease です。表のlinear相当の曲線ではx(t)=y(t)ですが、t自体がxと等しいわけではありません。なお、引数なしのキーワード linear と、複数点を折れ線で結ぶ関数 linear() は区別してください。

複数回跳ね返る動きは、1本の3次Bézierだけでは自由に再現できません。y'(t)は2次式なので、区間内の折り返し点は高々2つです。折れ線近似を指定する linear()、複数のキーフレーム、JavaScriptでの独自計算など、必要な動きに応じて表現方法を選びます。

フォントアウトラインと Bézier — TrueType は 2 次、OpenType CFF は 3 次

Bézier 曲線が「CSS のアニメーション」だけでなく「文字の形」にも使われていることは、あまり意識されません。

表が収まらない場合は、左右にスクロールできます。

フォーマットBézier 次数制御点数/セグメント設計者
TrueType (Apple/MS, 1991)2 次 (Quadratic)3 点 (始・制御・終)Apple
OpenType CFF / Type 1 (Adobe)3 次 (Cubic)4 点 (始・制御₁・制御₂・終)Adobe

TrueTypeアウトラインは2次Bézier、CFF/Type 1アウトラインは3次Bézierを採用しています。現在のOpenTypeは両方のアウトライン方式を扱えますが、ラスタライザー内部の具体的な近似・分割アルゴリズムは実装ごとに異なり得ます。

de Casteljau vs 直接展開 — 数値安定性

Bézier 曲線を計算する方法は大きく 2 つあります:

1. 多項式の直接展開 (Horner 法):

const a = 3 * x1 - 3 * x2 + 1;
const b = -6 * x1 + 3 * x2;
const c = 3 * x1;
const x = ((a * t + b) * t + c) * t;

係数a, b, cを事前計算した後の、1座標・1回の評価は乗算3回と加算2回です。係数計算や逆関数の反復コストは、この回数に含めていません。

2. de Casteljau アルゴリズム (線形補間の再帰):

// t での点を 3 段の lerp で求める
Q₀ = lerp(P₀, P₁, t)  // (1-t)P₀ + tP₁
Q₁ = lerp(P₁, P₂, t)
Q₂ = lerp(P₂, P₃, t)
R₀ = lerp(Q₀, Q₁, t)
R₁ = lerp(Q₁, Q₂, t)
B  = lerp(R₀, R₁, t)

3次のde Casteljau法は補間6回です。これは乗算6回という意味ではありません。例えば1座標の (1-t)*a+t*b は、1−tを事前計算しても乗算2回と加算1回が必要です。Horner法は計算をコンパクトにできますが、実行速度は実装と環境にも依存します。de Casteljau法は区間内の凸結合を繰り返すため安定な評価に適していますが、どちらも浮動小数点誤差を完全にはなくせません。

まとめ

  • CSS cubic-bezier() は Bézier 曲線を「形」ではなく「時間→進行度の関数」として使う
  • P₀=(0,0)、P₃=(1,1) 固定、ユーザーは P₁ と P₂ の座標のみ指定
  • x₁, x₂ ∈ [0,1] の制約は x(t) の単調増加 (時間が戻らない) を保証するため
  • y₁, y₂ は制限なし — [0,1] 外の値で overshoot (弾む) アニメーションを表現
  • x → t の逆問題はNewton-Raphson法や二分法などの数値解法で求める
  • Bernstein 基底の非負性と1の分割により、曲線は固定端点も含む全制御点の凸包内にある
  • ease系4キーワードはcubic-bezier()に対応。linearは独立したキーワードだが対角線の曲線と同じ出力を持つ
  • フォントアウトラインでも Bézier: TrueType = 2 次、OpenType CFF = 3 次

参考文献・ソース

記事作成に関する注記

本記事は AI(大規模言語モデル)を編集補助として活用して作成しています。 公開前に編集者が内容を確認していますが、事実誤認・仕様の解釈ミス・最新情報との齟齬が含まれる可能性があります。 重要な判断を行う際は、本文中の一次ソースや公式ドキュメントを必ずご自身でご確認ください。 誤りにお気づきの場合は、お問い合わせフォームよりご連絡いただけると助かります。

🔧 関連ツール

📚 関連記事