こんばんは。ドワンゴジェイピーという音楽配信サイトのバックエンドシステムの開発・保守・運用の仕事をしている、irimo と申します。
(大遅刻して申し訳ありません... これくらいゆるくていいのよ、と言いたいところですし、こういう時自責をしてしまうのはよくないのよ、とも言いたいところ)
今回は、gsap
という、Webフロントエンドのモーショントゥイーンライブラリを使って、プログラムに関係するアニメーションを作る際に参考になるお話ができればいいなと思いまして、テーマを決定しました。
ソートのアニメーション
おなじみバブルソートです。バブルソートのアルゴリズムはググれば出てきますが、そちらは本筋ではないため割愛します。
こちらは 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分をお約束しますので、ご興味があればご覧になってください(一応会社のアドカレなので書きました!)。
自分はこの作品が生きがいなのですが、寝る前にふと「ヒープソート」という言葉が浮かびましてですね。こんな動画が浮かびました。。
ヒプノシスマイクのキャラクターをヒープソートしたかった
のですが、ヒープソートは難しく、ちゃんとしたアニメーションを作ることは叶いませんでした。申し訳ありません。
上記の gif アニメの動画も最後まで見ると、惜しい!って感じると思います。
おわりに
ロジックを考えるのはエンジニアの醍醐味ですが、フロンティアがどんどんなくなっていっています。ですが、ゲームやアニメーションは可能性が無限大です。割と手抜き動画でも映える気がしますので、gsap をいじってみてはいかがでしょうか。
ご参考までに、今回のソースコードはこちらになります。github/irimo/sort-anima