ソートアルゴリズムのアニメーションをgsapで作ってみた

こんばんは。ドワンゴジェイピーという音楽配信サイトのバックエンドシステムの開発・保守・運用の仕事をしている、irimo と申します。
（大遅刻して申し訳ありません... これくらいゆるくていいのよ、と言いたいところですし、こういう時自責をしてしまうのはよくないのよ、とも言いたいところ）

今回は、`gsap` という、Webフロントエンドのモーショントゥイーンライブラリを使って、プログラムに関係するアニメーションを作る際に参考になるお話ができればいいなと思いまして、テーマを決定しました。

## ソートのアニメーション

こんな動画を作ってみました。
![bubblesort.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33738/a75fc2a6-dedc-e84f-f531-81f033c8c639.gif)

おなじみバブルソートです。バブルソートのアルゴリズムはググれば出てきますが、そちらは本筋ではないため割愛します。
こちらは gsap を利用して、バブルソートのロジック中にアニメーションを埋め込んでいます。

### gsap とは

公式によると、

`Professional-grade JavaScript animation for the modern web`

JS で Flash のようなアニメーションが作れるライブラリです。
初期値、終着地点、イージング可否、秒数を指定すると、よしなに動いてくれます。

### コツ1: Promise が無視されてしまうことの対策

このように使うのですが、

```typescript
var tl = gsap.timeline({repeat: 1});
tl.to(elem1, { // elem1 は HTMLElement が入る
    duration: 1, // 秒数
    x: "100px", // 0以外は単位をつける
    y: elem1_top_diff + "px", // 単位は gsap に渡すときにつけるのが best
});
tl.to(elem1, {
    duration: 1,
    x: "0",
    y: elem1_top_diff + "px",
});
tl.pause();
tl.resume();
```

こちら JS らしく、普通に書いていると非同期になってしまい、この後の処理をしながらアニメーションが走ってしまいます。
なので、アニメーションに指定している秒数 sleep をかませるといいです。
スマートじゃないですが...。

なのでこんな感じに

```typescript
var tl = gsap.timeline({repeat: 1});
var sleep_delay = 0;
tl.to(elem1, {
    duration: 1, // 秒数
    x: "100px", // 0以外は単位をつける
    y: elem1_top_diff + "px", // 単位は gsap に渡すときにつけるのが best
});
sleep_delay += 1000;  // tl に追加するたびに足していくと使いやすい

tl.to(elem1, {
    duration: 1,
    x: "0",
    y: elem1_top_diff + "px",
});
sleep_delay += 1000;

tl.pause();
tl.resume();

// こちら JavaScript sleep で検索してください（ありがとうございます！）
// 他のメソッドでもアニメーションをさせると思いますので、
// 使いまわせるように、クロージャにしないほうがいいかと
async this.sleep(sleep_delay);
```

### コツ2: 座標の指定について

オブジェクトは、初期値との相対座標で全てのモーションの座標を指定します。
そのため、初期値が謎の位置だと混乱することがあるので、初期値はわかりやすい座標にして、アニメーションの最初に `dulation: 0` （0秒で動く）で移動させてやると、座標の指定が直感的になります。
また、アニメーションの最初に `alpha: 1` にするとか、アニメーションの最後に `alpha:0` にする（両方 `dulation: 0` で）と、作りやすいかと思います。

先程の例だと

```typescript
var tl = gsap.timeline({repeat: 1});
var sleep_delay = 0;
elem1.style.top = 0;  // 初期値・0座標
elem1.style.left = 0;  // 初期値・0座標
tl.to(elem1, {
    duration: 0, // 秒数
    alpha: 1,  // 前回の処理で 0 になってるかもしれないのでパッと表示させる
    x: "100px", // 開始位置の例
    y: elem1_top_diff + "px", // 開始位置の例
});

tl.to(elem1, {
    duration: 1, // 秒数
    x: "10px", // 0以外は単位をつける
    y: elem1_top_diff + "px", // 単位は gsap に渡すときにつけるのが best
});
sleep_delay += 1000;  // timeline に追加するたびに足していくと使いやすい

tl.to(elem1, {
    duration: 1,
    x: "0",
    y: elem1_top_diff + "px",
});
sleep_delay += 1000;

tl.to(elem1, {
    duration: 0, // 秒数
    alpha: 0,  // 最後に消したい時はこうする
});

tl.pause();
tl.resume();

async this.sleep(sleep_delay);
```

てな感じです。

### コツ3: HTMLElement の座標取得について

こちらはアニメーションに限ったことではないですが、今回、ソートのアルゴリズムをアニメーションとして実装するにあたり気になった点です。
HTMLElement のプロバティを見て、座標(`style.top`, `style.left`)を別の HTMLElement に渡そうと思ったのですが、こちらうまく取れません。ですので、座標は変数で持つことをおすすめします。渡すことはできます。
その際、（さきほどのソースコードには書いたのですが）、 `px` などの単位は、変数ではなく、代入するときに添えると便利です。

## モチベーション

なぜ今回ソートのアルゴリズムのアニメーションを作ろうと思ったかというと...

ヒプノシスマイクってご存知ですか？今期アニメの第一話が、ニコ動でバズっていたので、ご覧になった方がいらしたら嬉しいです。楽しい30分をお約束しますので、ご興味があればご覧になってください（一応会社のアドカレなので書きました！）。

自分はこの作品が生きがいなのですが、寝る前にふと「ヒープソート」という言葉が浮かびましてですね。こんな動画が浮かびました。。

![hyp-sort.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33738/c540004c-c6a3-e0cb-c316-31965f7563b8.gif)

`ヒプノシスマイクのキャラクターをヒープソートしたかった` のですが、ヒープソートは難しく、ちゃんとしたアニメーションを作ることは叶いませんでした。申し訳ありません。
上記の gif アニメの動画も最後まで見ると、惜しい！って感じると思います。

## おわりに

ロジックを考えるのはエンジニアの醍醐味ですが、フロンティアがどんどんなくなっていっています。ですが、ゲームやアニメーションは可能性が無限大です。割と手抜き動画でも映える気がしますので、gsap をいじってみてはいかがでしょうか。

ご参考までに、今回のソースコードはこちらになります。[github/irimo/sort-anima](https://github.com/irimo/sort-anima)