はじめに
Javascriptのイージングアニメーションをさせるライブラリはあるが、p5.js上でどのように適用できるかいまいち分かりませんでした。
シンプルに実装できるみたいだったので、実装してみました。
以下のページにあるJavascriptプログラムを参考にしています。
easing.js
https://gist.github.com/gre/1650294
この記事では、以下のようなことを行っています。
- 参考プログラムをTypeScriptで実装する
- 実際に
位置
と大きさ
を変えるアニメーションを実装する
p5.jsをTypeScriptで書く環境を構築する方法については、以下の記事をご参照ください。
TypeScript+webpackでProcessing(p5.js)の環境を構築する
https://qiita.com/uchiko/items/744d7559d37973a959ea
今回のサンプルプログラムは以下にあります。
https://github.com/memememomo/ts-easing-anim-sample
サンプルプログラムの概要
今回作成したプログラムは、関係性を表すとクラス図に表すと以下のようになっています。
名前 | 概要 |
---|---|
Tween | イージングアニメーションを制御するクラス。 イージング関数を使用してアニメーションを実行する。 |
Easing | イージング関数の型。 イージング関数を実装する場合は、 この型に合わせるようにする。 |
Point | 座標とサイズを持った型。 アニメーションの対象になるオブジェクトは、 この型に合わせるようにする。 |
イージング関数の実装
まず、イージング関数の型を以下のように実装しています。
type Easing = (t: number) => number;
この型に合わせてそれぞれのイージング関数を実装します。
export const easeIn = p => t => Math.pow(t, p);
export const easeOut = p => t => 1 - Math.abs(Math.pow(t - 1, p));
export const easeInOut = p => t =>
t < 0.5 ? easeIn(p)(t * 2) / 2 : easeOut(p)(t * 2 - 1) / 2 + 0.5;
export const linear = easeInOut(1);
export const easeInQuad = easeIn(2);
export const easeOutQuad = easeOut(2);
export const easeInOutQuad = easeInOut(2);
export const easeInCubic = easeIn(3);
export const easeOutCubic = easeOut(3);
export const easeInOutCubic = easeInOut(3);
export const easeInQuart = easeIn(4);
export const easeOutQuart = easeOut(4);
export const easeInOutQuart = easeInOut(4);
export const easeInQuint = easeIn(5);
export const easeOutQuint = easeOut(5);
export const easeInOutQuint = easeInOut(5);
export const easeInElastic = t => (0.04 - 0.04 / t) * Math.sin(25 * t) + 1;
export const easeOutElastic = t => ((0.04 * t) / --t) * Math.sin(25 * t);
export const easeInOutElastic = t =>
(t -= 0.5) < 0
? (0.02 + 0.01 / t) * Math.sin(50 * t)
: (0.02 - 0.01 / t) * Math.sin(50 * t) + 1;
イージング関数を使用する流れは以下のようなイメージになります。
// アニメーション開始時刻から現在まで、どのくらいの時間が経過しているか計算
const diff = Date.now() - startTime;
// 全体のアニメーション再生時間から何割の時間が経過しているか計算
const t = diff / duration;
// イージング関数で計算
const rate = easingFunc(t);
// 線形補間で現在の値を算出。位置を変化させる場合は現在位置を算出することになる
const currentPos = startPos + (destPos - startPos) * rate;
アニメーション対象の実装について
サンプルプログラムでは、以下のような型を実装しています。
type Point = {
x?: number;
y?: number;
sizeX?: number;
sizeY?: number;
};
イージングアニメーション処理を扱うTweenクラスでは、この型に準じたオブジェクトに関して、アニメーション処理を行います。
サンプルプログラムでは、Ellipseクラスを定義しています。
import * as p5 from "p5";
export class Ellipse {
constructor(
private p: p5,
public x: number,
public y: number,
public sizeX: number,
public sizeY: number
) {}
draw() {
this.p.push();
this.p.ellipse(this.x, this.y, this.sizeX, this.sizeY);
this.p.pop();
}
}
Ellipseクラスは、 x
y
sizeX
sizeY
のメンバ変数を持っているため、Point型のオブジェクトとして扱えます。
Tweenクラスの実装
以下のような流れで使用できるTweenクラスを実装しています。
// preload()
ellipsePos = new Ellipse(p, 100, 100, 100, 100);
posEasing = new Tween(ellipsePos);
posEasing.to({ x: 600, y: 600 }, 2000);
posEasing.easing(easeInOutElastic);
posEasing.start();
// draw()
posEasing.update();
ellipsePos.draw();
Tweenクラスの実装は以下のようになっています。
export class Tween {
startPoint: Point;
destPoint: Point;
duration: number;
easingFunc: Easing;
startTime: number;
isMove: boolean;
delayTime: number;
constructor(private point: Point) {
this.startPoint = {
x: point.x,
y: point.y,
sizeX: point.sizeX,
sizeY: point.sizeY
};
this.easingFunc = linear;
this.isMove = false;
this.delayTime = 0;
}
to(to: Point, duration: number) {
this.destPoint = to;
this.duration = duration;
}
easing(e: Easing) {
this.easingFunc = e;
}
delay(d: number) {
this.delayTime = d;
}
start() {
this.startTime = Date.now();
this.isMove = true;
}
update() {
if (!this.isMove) {
return;
}
const diffTime = Date.now() - this.startTime - this.delayTime;
if (diffTime < 0) {
return;
}
const rate = this.easingFunc(diffTime / this.duration);
if (this.destPoint.x != null) {
this.point.x = lerp(this.startPoint.x, this.destPoint.x, rate);
}
if (this.destPoint.y != null) {
this.point.y = lerp(this.startPoint.y, this.destPoint.y, rate);
}
if (this.destPoint.sizeX != null) {
this.point.sizeX = lerp(
this.startPoint.sizeX,
this.destPoint.sizeX,
rate
);
}
if (this.destPoint.sizeY != null) {
this.point.sizeY = lerp(
this.startPoint.sizeY,
this.destPoint.sizeY,
rate
);
}
if (diffTime >= this.duration) {
this.isMove = false;
}
}
}
まとめ
シンプルなイージングアニメーションを実装しました。いまのところ必要になる処理はこれで十分まかなえています。
他のライブラリと比べると、まだまだ実装が足りない部分があると思います。もしかしたら、p5js用のライブラリとして作り込むかもしれません。