ブラウザ間で3Dシーンを同期: localStorageとThree.jsの革新的活用

Xでおもしろいアプリケーションを見つけたので紹介します。
https://github.com/bgstaal/multipleWindow3dScene
![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742770/ba25389e-182d-9650-585d-83f746c399b4.gif)

Three.jsとlocalStorageを使って、複数のブラウザウィンドウ間で3Dシーンを同期することで実現しています。
どのようにコードが書かれているか、重要な部分を抜粋して紹介していきます。

:::note info
3Dシーン
複数の3Dモデルや環境要素（例えば光源、カメラ配置、背景など）を組み合わせた、より包括的な立体的な表示環境のこと
:::

## 基本的な処理の流れ

1. **ウィンドウの初期化**:
   - 新しいウィンドウを作成し、既存のウィンドウと同期させます。
   - 同期は、`localStorage`に保存された状態情報を共有することで行われます。

2. **3Dシーンの初期化とレンダリング**:
   - 3Dシーンを設定し、レンダリングします。
   - ウィンドウのサイズが変更されると、シーンは自動的に更新されます。

3. **状態の同期**:
   - `localStorage`を介して、ウィンドウ間で3Dシーンの状態（例えば、カメラの位置やシーンのオブジェクトの配置）が同期されます。

## 実装の詳細
※「3Dシーンの初期化とレンダリング」の実装についてはThree.jsの知識がほとんどなので省きます。

### ウィンドウの初期化
```js
	// initiate current window (add metadata for custom data to store with each window instance)
	init (metaData)
	{
		this.#windows = JSON.parse(localStorage.getItem("windows")) || [];
		this.#count= localStorage.getItem("count") || 0;
		this.#count++;

		this.#id = this.#count;
		let shape = this.getWinShape();
		this.#winData = {id: this.#id, shape: shape, metaData: metaData};
		this.#windows.push(this.#winData);

		localStorage.setItem("count", this.#count);
		this.updateWindowsLocalStorage();
	}
```
- **ウィンドウリストの読み込み**:
  - `localStorage`から`windows`キーの値を読み込み、JSON形式で解析します。
  - もし`windows`キーが存在しない場合は、空の配列を使用します。

- **ウィンドウカウントの更新**:
  - `localStorage`から`count`キーの値を読み込み、カウントを1増やします。
  - このカウントは、新しいウィンドウの一意のIDとして使用されます。

- **ウィンドウデータの設定**:
  - 新しいウィンドウデータオブジェクトを作成し、その形状（`shape`）、ID、およびカスタムメタデータを設定します。
  - この新しいウィンドウデータを`#windows`に追加します。

- **localStorageの更新**:
  - `count`キーと`windows`キーの値を`localStorage`に保存します。

この関数によって、各ウィンドウは独自の識別子と状態を持ち、他のウィンドウとの同期が可能になります。

### 状態の同期

- **LocalStorageの変更を監視**:
  - 他のウィンドウでlocalStorageが変更された場合に反応します。
  - localStorageのeventからwindowキーの変更を監視し、新しいウィンドウ情報に基づいて現在の`#windows`を更新します。
  - `#windows`の変更があった場合、設定されたコールバック関数を実行し、3Dシーンの状態を更新します。

```js
		// event listener for when localStorage is changed from another window
		addEventListener("storage", (event) => 
		{
			if (event.key == "windows")
			{
				let newWindows = JSON.parse(event.newValue);
				let winChange = that.#didWindowsChange(that.#windows, newWindows);

				that.#windows = newWindows;

				if (winChange)
				{
					if (that.#winChangeCallback) that.#winChangeCallback();
				}
			}
		});
```

- **ウィンドウのクローズイベント処理**:
  - 現在のウィンドウが閉じられる前に、そのウィンドウを`#windows`から削除し、localStorageの情報を更新します。

```js
		// event listener for when current window is about to ble closed
		window.addEventListener('beforeunload', function (e) 
		{
			let index = that.getWindowIndexFromId(that.#id);

			//remove this window from the list and update local storage
			that.#windows.splice(index, 1);
			that.updateWindowsLocalStorage();
		});
```
## localStorageの値
以下はwindowsキーに保存されたJSONデータの例です。このデータには、各ウィンドウのID、形状（位置とサイズ）、およびカスタムメタデータが含まれます。
```json
[
   {
      "id":20,
      "shape":{
         "x":18,
         "y":43,
         "w":1257,
         "h":675
      },
      "metaData":{
         "foo":"bar"
      }
   },
   {
      "id":21,
      "shape":{
         "x":477,
         "y":168,
         "w":1977,
         "h":1092
      },
      "metaData":{
         "foo":"bar"
      }
   },
   {
      "id":22,
      "shape":{
         "x":64,
         "y":246,
         "w":1819,
         "h":716
      },
      "metaData":{
         "foo":"bar"
      }
   }
]
```

## 最後に
あまりwindow間のことを考えてこなかったこともあり、localStorageがwindow間で共有されていることを初めて知りました。
他にもこの技術を使ってアプリケーションを作った方がXなどで投稿していたのでぜひ探してみてください！

## PR
HRBrainでは一緒に働く仲間を募集しています！
Advent Calendarを見ていただき、興味を持っていただいた方はぜひ弊社の採用ページをご確認ください！

https://www.hrbrain.co.jp/recruit