JavaScriptで要素をフェードイン・フェードアウトで切り替える ―async/awaitでrequestAnimationFrameを使う―

妻の歌人としての個人サイトのトップページで、短歌をアニメーションで切り替える処理をJavaScriptで書いている。

jQueryには上のような専用メソッドがあるフェードイン・フェードアウト処理を自分でググりつつ書いてみたところ、試行錯誤できるポイントがあったのでまとめておきたい。

setTimeoutを使った当初実装

まずjQueryの実装は高度に共通化されていて読み解くのがなかなか大変そうだったので、ググって実装方法を探してみた。

このStack Overflowの回答が分かりやすかったので、参考にして実装してみた。

それがこれ。ループでsetTimeoutを使って100ミリ秒ごとに少しずつopacity(不透明度)を変化させている。

分かりやすい処理でちゃんとフェードイン・フェードアウトしたので満足しつつ、念の為他のサイトの実装例も色々と見てみたらもっと良い実装方法がありそうだった。

requestAnimationFrameを使ってより滑らかなアニメーションを実現

このサイトの実装例は明らかに私の当初実装より洗練されているように見えた。requestAnimationFrameというメソッドを使うと良いらしい。

このコールバックの回数は、たいてい毎秒 60 回ですが、一般的に多くのブラウザーでは W3C の勧告に従って、ディスプレイのリフレッシュレートに合わせて行われます。

ただし、setTimeout や setInterval は、ブラウザー側で再描画の準備が整っているか否かにかかわらず、必ず実行されてしまいます。また、ブラウザーのタブが非表示 (バックグラウンド) の場合でも常に実行し続けます。

一方で、requestAnimationFrame はブラウザーの負荷に合わせては 60 FPS 以内で再描画の準備が整ったタイミングで実行され、また、ブラウザーのタブが非表示 (バックグラウンド) にある場合は、発火頻度が自動で低下します。これにより、メモリーの消費を抑えることができます。

こういった理由で requestAnimationFrame はタイマーメソッドより、アニメーションの表現に向いていると言えます。一方で、正確な FPS を制御することはできません。

いい感じにブラウザのフレームごとにタイムスタンプを引数にしてコールバック関数を実行してくれるらしい。

このrequestAnimationFrameを使って上のように実装し直してみた。当初の100ミリ秒ごとの不透明度変更と比べて明らかにアニメーションが滑らかになっている。第2引数にフェードイン/フェードアウト持続時間durationを指定できるようになったのも分かりやすい。

async/awaitの利用

参考サイトと違うのはasync/awaitを使っている点で、

コールバック関数の引数にタイムスタンプが渡されることに注目して、1フレーム進んだ後にPromiseがタイムスタンプを引数にresolveして、const timeStampに1フレーム進んだタイムスタンプが代入される仕組みになっている。

その他の参考記事