4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-13

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

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

ソートのアニメーション

こんな動画を作ってみました。
bubblesort.gif

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

gsap とは

公式によると、

Professional-grade JavaScript animation for the modern web

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

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

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

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 をかませるといいです。
スマートじゃないですが...。

なのでこんな感じに

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 で)と、作りやすいかと思います。

先程の例だと

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

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

おわりに

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

ご参考までに、今回のソースコードはこちらになります。github/irimo/sort-anima

4
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?