別タブで開いているページを、リロードすることなく更新したい(理論編)
概要
業務にて、以下の技術課題を解決する機会がありました。
- 目的のページを既に別タブで開いている場合、このタブを表示する
- タブを表示する際、リロードを行わずに画面のデータを更新する
- 画面のデータ : 上記例で言うと、選択されたチャプター動画の再生位置)
これらの実装方法について、調査結果を投下します。
書くこと/書かないこと
書くこと : 上記技術課題の解決例
書かないこと : 実装例(※)
(※)サンプルコードを交えた実装例は、以下の記事で取り上げます。
事の始まり
(※かなりぼかして書いています)
メディア再生機能を持つプロダクトを開発しています。
このプロダクトは、Webアプリケーションとして以下のページを提供します。
- メディア再生ページ
- メディアの紹介ページ
メディア再生ページでは、メディアの任意の位置に、名前付きリンクを発行できる機能(いわゆる Youtube の chapter 機能に相当)を提供します。
そんなプロダクトですが、ある時、以下のような要望が出ました。
- メディア紹介ページ
/intro
から、メディア再生ページ/video
への任意の chapter へ遷移したい-
/video
ページへ遷移しつつ、クリックされた chapter 位置まで自動でシークしてほしい
-
- 遷移する時、別タブで開いてほしい
- この時、既に別タブで
/video
を開いていたら、(新たにタブを開くのではなく)このタブを再利用してほしい - タブを再利用する場合は、画面リロードを行わず、シーク処理のみ行うこと
- 同メディア紹介ページの、異なる chapter へのリンクをクリックした場合も同様(リロードなし&タブ再利用)
この要望をきっかけに、色々と技術調査することになりました。
調査
この要望から技術課題を抽出すると、以下のようになります。
- 目的のページを既に別タブで開いている場合、このタブを表示する
- タブを表示する際、リロードを行わずに画面のデータを更新する
- 画面のデータ : 上記例で言うと、選択されたチャプター動画の再生位置)
それぞれ、実現可能性を調査します。
1.目的のページを既に別タブで開いている場合、このタブを表示する
リンクの target
属性の値を工夫することで、既存のタブを開く(≒タブを再利用する)ことが出来そうです。
- 新規タブで開くだけなら、
target
に_blank
を指定するだけでOK -
target
に、_blank
ではなく意味のある名前を指定することで、タブ(ウィンドウ)を再利用できる
target 属性に意味のある名前を提供し、ページ内で target 属性を再利用するようにしてください。
そうすれば、別のリンクをクリックしたときに、すでに作成・表示されているウィンドウに参照先のリソースが読み込まれ(したがってユーザーの処理速度が速くなり)、第二ウィンドウを作成した当初の理由(およびユーザーのシステムリソース、費やした時間)が正当化されることになります
キーバリューストアで実装したキャッシュをイメージしました。
-
target
がキーで、開くことになる別タブがバリュー
制約
URL さえ合っていれば、任意のタブを再利用してくれる ... わけではないようです。
target
を設定(例えば target=hoge
)したリンクから別タブを開く場合、
同じtarget
(target=hoge
)を設定しているリンクから開いたタブが既に存在していれば、
そのタブを再利用します。
2. タブを表示する際、リロードを行わずに画面のデータを更新する
ページ内アンカーリンクを踏んだ際と同じ挙動を目指します。
- ページ内アンカーリンクを踏むと、同一ページ内の特定位置へ遷移
- ブラウザの「戻る」ボタンを押すと、画面リロードを行わず、リンクを踏む前の状態に戻る
- 例) Qiita や Zenn の目次など
これは、ブラウザの履歴を操作出来る History API を駆使することで実現できそうです。
- 履歴を追加する
pushState
を実行し、カスタムデータを含む履歴(state
オブジェクト) をプッシュ -
dispatchEvent
にてpopstate
イベントを能動的に発火 -
popstate
イベント発火時に実行したい処理を定義し、その中でプッシュされた履歴(state
オブジェクト) を取り出して色々処理する
popstate
イベント自体は、 "ブラウザの「戻る」「進む」をクリックした際など、ブラウザの操作によって発火するイベント" とのことです。
popstate イベントは、戻るボタンや進むボタンをクリックする(あるいは JavaScript で history.back() や history.forward() を呼び出す)など、ブラウザーの操作によって発行されます。
引用元 : https://developer.mozilla.org/ja/docs/Web/API/Window/popstate_event
ページ内アンカーリンクの時のように、何らかの条件を満たすと、リロードなしで画面遷移が可能となります。
繰り返しになりますが、ページ内リンクの時と同じ挙動を再現することで、リロードの抑制を試みます。
全体的な処理の流れ
◆イベント発火側
-
window.open
にて、別タブで開く処理を実装 -
window.open
の戻り値(Window
インスタンス)から、開いた別タブの情報を得られるので、これを状態管理- これを使いまわすことで、リンクを踏む際のリロードを避ける
-
Window
インスタンスを通して、開いた別タブに対して様々な処理を行う- HIstory API 経由で、「新しい履歴(
state
オブジェクト) 」を追加する- 「新しい履歴(
state
オブジェクト) 」にカスタムデータを格納することで、別タブ側へデータを送信できる
- 「新しい履歴(
-
popstate
イベントを能動的に発行し、先ほど発行した「新しい履歴(state
オブジェクト)を取り出させる - 能動的にフォーカスさせる
- HIstory API 経由で、「新しい履歴(
◆イベント受信側
-
addEventListerner
にてpopstate
イベントを待ち受ける -
popsate
イベントを受信したら、state
オブジェクトからカスタムデータを受け取る - カスタムデータを使用した処理を実行する
- 一部コンポーネントだけ再描画など
結論
技術的には可能そうです。
以下の記事で、サンプルコードを交えた実装例を取り上げます。
Discussion