この記事は DeNA 24 新卒 Advent Calendar 2023 の 24 日目の記事です。
はじめに
DeNA24卒のアドベントカレンダーとして最近製作したウェブブラウザで動くマラカスについて書こうと思います。
ぜひぜひ皆さん振って踊って楽しんでください。筆者は毎日おもむろにマラカスを開いては鳴らして楽しんでおります。
この記事は私の作ったマラカスの紹介とともに、ウェブフロントにおけるパフォーマンス改善について、私の今できる全力で取り組んだことのまとめになります。皆さんのウェブフロントの知識の理解の助けになれば幸いです。
流し読みだけでもいろんな知識が身に付くように書いたので、ぜひ最後までご覧ください。
マラカスの使い方
まず、マラカスの使い方を解説します。
-
Webマラカスのサイトにアクセス
スマートフォンからアクセスすると、マラカスをよりリアルに楽しむことができます。 - お好みのマラカスを選択
3種類の用途に合わせた魅力的なマラカスがございます。 - モバイルデバイスのアイコンをクリックして、デバイスの加速度センサーへのアクセスを許可
- ミュートを解除
あとはマラカスを振って楽しむだけです。
🎉たくさん振って盛り上がりましょう🎉
もし、音が鳴らない場合はスマートフォンの消音モードを解除してください。
iphoneであればデバイスの左側にある物理トグルボタンです。
技術構成
マラカスを振るためにこの記事にたどり着いた方はここで離脱して、マラカスを振っていただいて構いません。
ここからはマラカスのために心血を注いだ技術を書いていきます。
このプロジェクトの技術構成にはNext.js と Three.jsを採用しました。UIデザインには、tailwindとChakraUIを組み合わせて使用しています。
マラカス実装の概要
今回のマラカスは主に二つのパーツから成り立ちます。
- マラカスの音声を鳴らすパーツ
- 3Dモデルを表示するパーツ
それぞれのパーツについて、以下で詳しく解説します。
マラカスの音声パーツの実装について
マラカスの音声を再生するためには、通常のaudioタグだけでは不十分です。
音声を鳴らすにはまず以下のような実装が思いつくと思います。
<figure>
<figcaption>Listen to the T-Rex:</figcaption>
<audio controls src="/media/cc0-audio/t-rex-roar.mp3">
<a href="/media/cc0-audio/t-rex-roar.mp3"> Download audio </a>
</audio>
</figure>
しかし、これでは今回のマラカスという要件は満たせません。
そこで、もっと強力なウェブの音声制御ライブラリである。Web Audio APIを利用します。
このライブラリを利用することで、マラカスのスムーズな音声再生を実現します。
ブラウザはシングルスレッドでしか動かない
マラカスを作っていくにあたって、最も気を付けたことはパフォーマンスです。
パフォーマンスを上げるために、ウェブブラウザの実行の基礎的な部分の理解が必要と思い、いろいろ調べましたが、一番のネックとなる部分はこのブラウザはシングルスレッドでしか動かないということです。
Chromeよ、たくさんのプロセスでいつもパソコンのメモリを圧迫しているのではないか、と。
しかし、基本的には一つのページは一つのスレッドで動きます。これがメインスレッドと呼ばれているものです。 ブラウザは基本的にメインスレッド上で動作するため、一度に一つのタスクしか処理できません。これがパフォーマンスに大きな影響を与えるポイントです。
つまり、非同期処理と呼ばれる処理を書いても、最終的に実行されるのはメインスレッドなので、すぐに実行されるか、Promiseがresolveされてから実行するかの違いしかありません。マルチスレッドや、マルチプロセスなどといった知識を知っていても、ここでは意味がないのです。
マイクロタスクキューとタスクキュー
とはいえ、ブラウザの操作において、OS視点から見ると、待ち状態の場合が非常に多いと思います。
ですから、すべて同一スレッドで実行しても普段の利用上は問題ありません。
ただ、マラカスの音を鳴らすとなると、常にデバイスを監視しておきたいですよね。
そこで今回はEventListenerを用いてデバイスの加速度を監視しました。
useEffect(() => {
if (isDevicemotionPermissionGranted) {
window.addEventListener("devicemotion", handleShake);
}
return () => {
window.removeEventListener("devicemotion", handleShake);
};
}, [handleShake, isDevicemotionPermissionGranted]);
実装自体は非常にシンプルで、devicemotionというイベントを監視して、そのイベントが発火すると、handleShake関数を実行しています。ただし、このイベントとてもたくさん発火します。そのため、少しコードの記述に失敗するとメインスレッドを圧迫してしまいます。これを解決したのがタスクキューです。
タスクキュー
タスクとは、プログラムの初期実行、イベントコールバックの実行、インターバルやタイムアウトの発生など、標準的なメカニズムによって実行がスケジュールされる JavaScript コードのことです。これらはすべてタスクキューにスケジューリングされます。
タスクは、以下の場合にタスクキューに追加されます。
新しい JavaScript プログラムやサブプログラムが(コンソールから、あるいは <script> 要素内のコードを実行して)直接実行されたとき。
イベントが発生し、イベントのコールバック関数がタスクキューに追加された場合。
setTimeout() または setInterval() で作成したタイムアウトまたはインターバルに達すると、対応するコールバックがタスクキューに追加されます。
イベントが発火すると、コールバック関数はこのタスクキューに入れられます。そして、JavaScriptのイベントループの中でタスクキューが一つずつピックアップされて実行されていきます。今回はマラカスの音を鳴らす関数がタスクに入れられて順番に実行されていきます。
マイクロタスクキュー
タスクキューと似たようなものにマイクロタスクキューというものがあります。
mdnによると、このように記載されています。
マイクロタスクは、それを作成した関数やプログラムが終了した後、 JavaScript 実行スタックが空の場合にのみ実行され、ユーザーエージェントがスクリプトの実行環境を動かすために使用しているイベントループにコントロールを返す前に実行される短い関数です。
マイクロタスクキューは、メインのタスクキューよりも優先度が高い小さなタスクを処理する仕組みです。しかし、マラカスの音声再生には、これを最小限に抑えることが重要です。そのため、今回のマラカスの実装では、このマイクロタスクキューは出来るだけ無い状態にしました。
また、ちょっとしたTipなのですが、音声再生はprimiseだと動きません。ですから、よりマイクロタスクキューを使う意義が薄れていきます。
デバイスの振動感知をWeb Workersで行わなかった理由
少し前の話に戻りますが、Web workersを利用するとメインスレッドとは別のスレッドでJavaScriptを実行することができます。しかし、devicemotionイベントの検知はメインスレッドで行わないといけません。Web WorkersはDOMにアクセスできないからです。また、イベントが発火した瞬間に、欲しい加速度のデータなどが既に引数として渡されています。そのため、今回はWeb Workersは利用しませんでした。今後Web Workersを使うような処理の重いアプリも作ってみたいです。
そろそろ記事を読み疲れたところかもしれません。
ぜひマラカスを振って休憩でもしてください。
マラカスのオブジェクト部分の実装について
マラカスのオブジェクトはThree.jsを用いて実装しました。
なんと、今回のマラカスはBlenderでの自作です。
そのBlenderで作成したオブジェクトをThree.jsで読み込んでいます。
メモ化
3Dオブジェクトを読み込むという行為はとてもデバイスに負荷を掛けます。
もともと、音声再生の方でもかなり大きな負荷がかかっているので、3Dオブジェクトの実装も気を抜けません。
今回は出来る限りメモ化をすることでマラカスのオブジェクトをスムーズに動かせるようにしました。
メモ化をすることで音声再生のタスクに対してより多くのCPU処理を割り当てられます。
メモ化については詳しくは公式リファレンスをご覧ください。
Reactのメモ化のリファレンスはこちら
ダークモード対応
このマラカスはダークモードにも対応しています。
暗い部屋でもきれいで目に優しいマラカスを楽しめます。
また、激しい点滅はせず、ミラーボールに照らされているマラカスにしてあります。
そのため、ダークモードにした瞬間にあなたの心はパーティー会場にいざなわれます。
この機能はChakraUIのダークモードを利用して実装しました。
SEO対策について
さらに、このプロジェクトのSEO対策にも注力しました。
参考にした記事はこちらです
今回のマラカスは、コンテンツ量が多いように見えて、とても少ないです。通常のウェブサイトは作成すると、文字がたくさん入るのですが、このマラカスは文字が少ないです。
そのため、Googleのインデックス登録にとても苦労しました。
改善したところは
- title, desctiptionの文字、文字数の工夫
- OGP画像の作成
- ページの中に有用な文字を入れる
これらのことをして、やっとGoogle先生にインデックス登録していただきました。
Google先生ありがとうございます。
ただし、記事執筆時点ではトップページしかGoogleのインデックス登録がされていません。 引き続きGoogle先生とは戦っていこうと思います。
また、これだけではやっとGoogle検索に出るようになっただけです。
SEOでいえば、5ページ目とか6ページ目とかに辛うじて出るような感じでした。
最近は身内にマラカスを紹介して振りまくっているので、徐々に検索のランクが上がってきましたが、まだまだです。
検索方法
この記事を読んでいただいた優しい読者様。ぜひ「マラカス ウェブアプリ」、「Web マラカス」、「マラカス アプリ」などで検索してみてください。もし検索していただけたら筆者が泣いて喜びます。
まとめ
以上がマラカスを作る上でいろいろ試行錯誤したことです。
Githubレポジトリはこちらです。
記事に書いた部分以外にもごりごりにカスタムフックに分けて責務ごとにコード分割をして出来るだけ可読性を上げられるように頑張りました。まだまだ修行中の身ですが、ちょっと覗いていただけると幸いです。
Star、Fork、PRも大歓迎です。
おわりに
最後まで読んでいただいてありがとうございました。
マラカスを全力で作っていると様々なウェブフロントに関する知見が得られて、とてもいい開発でした。
次のDeNA 24 新卒 Advent Calendar 2023の記事は@aki-nishikawaさんです。
最後までDeNA 24 新卒 Advent Calendar 2023をよろしくお願いします!!!