ikapblog技術関連のメモや調べたことをメモしていきますホーム文字in画像QRIPアドレス確認Javascriptで非インタラクティブなアニメーションを作る2021.01.24冬っぽい雪が降るだけのアニメーションをjavascriptで作った。動作が重くなりがちアニメーションの起点とブラウザの仕様おわりデモずっと動いたままになるので、バッテリー残量注意。ikapper/anim-fuyu-yuki使ったものcanvasを直に操作するのは辛いので、fabricを使用した。Libraryリソースはwebpackでバンドルしている。していること開いたときに表示されているものが全てだが、一応書いておく。false});レンダリングしたいときは、fabcanvas.renderAll()を実行する。レンダリングを行うタイミングは、初期化後と雪を下方向に動かした後の2つ。後者は何度もコールする。fabcanvas.setBackgroundColor('#e2e2e2');とするだけ。家は自作したsvgイメージを読み込むことにした。fabricのStaticCanvasのインスタンス。初期化時に代入される。});}上方から順に説明していく。屋根色をランダムにするために、何色か用意してリストで保持している。引数のscalingは拡大縮小の倍率で、生成するsvgの大きさで調整が必要。今回は0.5と0.3で生成している。関数の実行時には、最初にcanvasの幅と高さを取得している。定数で持っていてもいいのだが、ウィンドウのリサイズにも対応するようにしているため、各関数で取得するようにしている。家のレンダリングは最初の1回だけなのであまり関係はない。fabric.loadSVGFromURL()は画像パスとコールバックを指定してsvgをロードできる関数。oImgは(確か)fabric.Imageのインスタンスのリストだったので、fabric.Groupで1つのhouseにまとめている。houseの大きさをセットして、canvas上の位置を決めている。house.item(3)は屋根部分で、かつstrokeで着色しているので、strokeを上書きしている。fabric.StaticCanvas.add()はレイヤ的に考えると最上位に追加されるようだ。アニメーションされる雪が奥側に行ってしまうと少し不自然になるので、奥側にhouseを配置するためにfabric.StaticCanvas.insertAt()を使って挿入位置を指定している。実際を考えたら奥に行く雪もあるが考慮してない。スケール倍率を指定してこの関数を呼び出せば、canvasに家を描画できる。重なったときの違和感はご愛嬌。雪は円で表現している。大きさの異なる円をcanvas上に散らせておけばそれっぽく見えるのは不思議な感じもする。snowsは、アニメーション用に関数外で保持する。fabcanvasから取得もできるが、家との区別が面倒なのでこの形にした。次に雪の最大個数をSNOW_COUNT_MAXで定義しておく。数を大きくしすぎるとかくつくことがある。家の屋根と同じく、雪の色も5通り用意しておく。関数の外側はこれで終わり、今度は中身に移る。家の方で述べたとおり、まず最初にcanvasの幅・高さを取得する。次にループで雪となる円を生成していく。fabric.Circleをnewすればいい。半径・塗りつぶし色・位置の順で指定している。topとleftはcanvasの左上からの位置。その後、アニメーションで使うvelYを設定する。下方向へ動く速度になる。単位はpixel/sec。範囲は[10,25)とした。最後に、fabcanvasとsnowsに追加する。アニメーション雪を下方向へ動かすアニメーションを行いたい。fabric.util.requestAnimFrame()を利用する。window.requestAnimationFrame()の改良版らしい。fabcanvas);};reqAnimFrame();正しい使い方をしている自信があまりない。普通に動いているのでいいだろう。前回実行時の時間を保持するために外側にtimestampを持つ。最近無名関数でも自身を呼び出して再帰できることを知ったが、ここではそれは行ってないので、関数を定義してその名前で再帰している。ページのロード完了時からの経過時間を返してくれていると思うので、timestampで前回実行時間を保持し、渡されてきたtimeとの差を比較し、経過時間timedeltaを知る。そのあとにtimestampを更新する。(改善点有り、後述)またこれまで通りcanvasの幅・高さを取得。ループでsnowsの中身のfabric.Circleインスタンス達の位置を更新する。速度velYに基づいて次の高さを決める。その高さがcanvasの高さを超えたら、今度は天辺から描画し直す。雪の大きさの分だけ上にはみ出させて違和感を軽減している。天辺からの時は、横方向の座標を再度ランダムに決定する。全ての雪(円)について更新が終わったらcanvasに再描画を促す。再帰するまでの処理で時間がかかりすぎると動作が重くなってしまう。改善点とりあえず思いつくのは2点ある。修正するかもしれない改善点。動作が重くなりがちもう少し動作を軽くしたい。これだけなのに割と重く感じる。原因はやはり雪となる円のせいだろう。円を1つずつ描くのはやはり効率が悪い気がする。雪のグループの画を何枚か用意して重ねれば簡単に増やせるとは思うが、ランダム性は薄くなってしまう気がする。そもそもそんな簡単に軽くなるかも分からないが...アニメーションの起点とブラウザの仕様requestAnimFrame()でtimestampの初期値はnullにしておくべきだった。しないと、最初のreqAnimFrame()のtimedeltaの値が正確で無くなってしまうためだ。ブラウザの省エネ設計により、他タブを開くなどでcanvasが隠れてしまうと、requestAnimationFrame()の更新頻度がかなり下がり、結果timedeltaが大きくなり、雪が画面外の下方へ行ったと判定されて、全て上方へ行ってしまうという問題もある。これは、timedeltaが大きすぎたら適当な値(1000/60など)に調整することでいい感じにごまかすことはできると思う。setTimeout,setIntervalなどを使う方法もある。あれらはおそらく他タブを開いても実行される?と思う(未確認)。おわりもう少し色々配置したいがsvgを作るのが大変だ。作るのに慣れるべきだ。見た目も改善できるし。眺めてて面白いものを作りたいものだ。