Gojabako ZoneKei Ito

ベジエ曲線の描画

に公開に更新)履歴 (2)

別の記事でアニメーションをつけるのにベジエ曲線が必要だったので、つくりがてら記事にすることにしました。

ベジエ曲線はいくつかの制御点から得られる曲線で、制御点の数によって3つなら2次ベジエ曲線、4つなら3次ベジエ曲線などと呼ばれます。私がほしかったのは3次なのでこれ以降は3次限定です。

## 3次ベジエ曲線

3次ベジエ曲線は4つの制御点からなります。それぞれ位置を とすると曲線上の点 は次式で表されます。

ただし です。 になります。

(t: number) => [number, number] を返す getCubicBezierFunction は次のように書けます。

1type Point = [number, number];2const getCubicBezierFunction = (3 [x0, y0]: Point,4 [x1, y1]: Point,5 [x2, y2]: Point,6 [x3, y3]: Point,7) => (t1: number): Point => {8 const t2 = t1 ** 2, t3 = t1 ** 3;9 const u1 = 1 - t1, u2 = u1 ** 2, u3 = u1 ** 3;10 return [11 u3 * x0 + 3 * u2 * t1 * x1 + 3 * u1 * t2 * x2 + t3 * x3,12 u3 * y0 + 3 * u2 * t1 * y1 + 3 * u1 * t2 * y2 + t3 * y3,13 ];14};

次のサンプルは上記の関数を使っており、 に対応する を確認できます。

制御点はマウスまたは矢印キーで移動できます。t は下のスライダーで変更できます。
cubicBezierの動作確認アプリケーション
t = 0.40

これで から が求められるようになりましたが、私がほしかったのは CSS の cubic-bezier(0.42,0,0.58,1) のように使えるもので、以下のようにアニメーションの進み方を指定するものです。

cubic-bezier(0.42, 0, 0.58, 1) は最初と最後がゆっくり動きます。
timingFunctionの動作確認アプリケーション

が時間で、曲線との交点の 座標がアニメーションの進捗になっています。つまり、ほしいのは から を求める関数です。

は固定なので、 は次のようになります。

これを について解こうとすると が一意に定まらないケースがあって困ります。仕方がないので 個の を求めておいて、内分点で近似することにしました。

1type Point = [number, number];2const getTimingFunction = (p1: Point, p2: Point, N = 20) => {3 const samples: Array<Point> = [4 [0, 0], // p05 ...(function* () {6 const bezier = getCubicBezierFunction([0, 0], p1, p2, [1, 1]);7 const step = 1 / N;8 for (let t = step; t < 1; t += step) {9 yield bezier(t);10 }11 })(),12 [1, 1], // p313 ];14 return (x: number): number => {15 if (x <= 0) {16 return 0; // p0[0]17 }18 if (1 <= x) {19 return 1; // p3[0]20 }21 let index = samples.findIndex((point) => x < point[0]);22 const [x1, y1] = samples[index - 1];23 const [x2, y2] = samples[index];24 const r = (x - x1) / (x2 - x1);25 return y1 * (1 - r) + y2 * r;26 };27};

ちょうどいい はいくつなのかという問題ですが、で良さそうでした。 だと両端の動きにぎこちなさがありますが、アニメーションの周期 が小さければ気にならないですね。以下では を変更できるようにしているので試してみてください。

制御点はマウスまたは矢印キーで移動できます。
timingFunctionの動作確認アプリケーション
T = 2000 ms
N = 15

<easing-function> の ease, ease-in, ease-out, ease-in-out は次のように書けます。

1const ease = getTimingFunction([0.25, 0.1], [0.25, 1.0]);2const easeIn = getTimingFunction([0.42, 0.0], [1.00, 1.0]);3const easeOut = getTimingFunction([0.00, 0.0], [0.58, 1.0]);4const easeInOut = getTimingFunction([0.42, 0.0], [0.58, 1.0]);

以上です。