😺

別タブで開いているページを、リロードすることなく更新したい(理論編)

2024/09/16に公開

概要

業務にて、以下の技術課題を解決する機会がありました。

  1. 目的のページを既に別タブで開いている場合、このタブを表示する
  2. タブを表示する際、リロードを行わずに画面のデータを更新する
    • 画面のデータ : 上記例で言うと、選択されたチャプター動画の再生位置)

これらの実装方法について、調査結果を投下します。

書くこと/書かないこと

書くこと : 上記技術課題の解決例
書かないこと : 実装例(※)

(※)サンプルコードを交えた実装例は、以下の記事で取り上げます。

事の始まり

(※かなりぼかして書いています)

メディア再生機能を持つプロダクトを開発しています。

このプロダクトは、Webアプリケーションとして以下のページを提供します。

  • メディア再生ページ
  • メディアの紹介ページ

メディア再生ページでは、メディアの任意の位置に、名前付きリンクを発行できる機能(いわゆる Youtube の chapter 機能に相当)を提供します。

そんなプロダクトですが、ある時、以下のような要望が出ました。

  • メディア紹介ページ /intro から、メディア再生ページ /video への任意の chapter へ遷移したい
    • /video ページへ遷移しつつ、クリックされた chapter 位置まで自動でシークしてほしい
  • 遷移する時、別タブで開いてほしい
  • この時、既に別タブで /video を開いていたら、(新たにタブを開くのではなく)このタブを再利用してほしい
  • タブを再利用する場合は、画面リロードを行わず、シーク処理のみ行うこと
    • 同メディア紹介ページの、異なる chapter へのリンクをクリックした場合も同様(リロードなし&タブ再利用)

イメージ

この要望をきっかけに、色々と技術調査することになりました。

調査

この要望から技術課題を抽出すると、以下のようになります。

  1. 目的のページを既に別タブで開いている場合、このタブを表示する
  2. タブを表示する際、リロードを行わずに画面のデータを更新する
    • 画面のデータ : 上記例で言うと、選択されたチャプター動画の再生位置)

それぞれ、実現可能性を調査します。

1.目的のページを既に別タブで開いている場合、このタブを表示する

リンクの target 属性の値を工夫することで、既存のタブを開く(≒タブを再利用する)ことが出来そうです。

  • 新規タブで開くだけなら、 target_blank を指定するだけでOK
  • target に、 _blank ではなく意味のある名前を指定することで、タブ(ウィンドウ)を再利用できる

target 属性に意味のある名前を提供し、ページ内で target 属性を再利用するようにしてください。
そうすれば、別のリンクをクリックしたときに、すでに作成・表示されているウィンドウに参照先のリソースが読み込まれ(したがってユーザーの処理速度が速くなり)、第二ウィンドウを作成した当初の理由(およびユーザーのシステムリソース、費やした時間)が正当化されることになります

キーバリューストアで実装したキャッシュをイメージしました。

  • target がキーで、開くことになる別タブがバリュー

制約

URL さえ合っていれば、任意のタブを再利用してくれる ... わけではないようです。

target を設定(例えば target=hoge )したリンクから別タブを開く場合、
同じtargettarget=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

ページ内アンカーリンクの時のように、何らかの条件を満たすと、リロードなしで画面遷移が可能となります。

繰り返しになりますが、ページ内リンクの時と同じ挙動を再現することで、リロードの抑制を試みます。

全体的な処理の流れ

◆イベント発火側

  1. window.open にて、別タブで開く処理を実装
  2. window.openの戻り値( Window インスタンス)から、開いた別タブの情報を得られるので、これを状態管理
    • これを使いまわすことで、リンクを踏む際のリロードを避ける
  3. Window インスタンスを通して、開いた別タブに対して様々な処理を行う
    • HIstory API 経由で、「新しい履歴( state オブジェクト) 」を追加する
      • 「新しい履歴( state オブジェクト) 」にカスタムデータを格納することで、別タブ側へデータを送信できる
    • popstate イベントを能動的に発行し、先ほど発行した「新しい履歴( state オブジェクト)を取り出させる
    • 能動的にフォーカスさせる

◆イベント受信側

  1. addEventListerner にて popstate イベントを待ち受ける
  2. popsate イベントを受信したら、 state オブジェクトからカスタムデータを受け取る
  3. カスタムデータを使用した処理を実行する
    • 一部コンポーネントだけ再描画など

イメージ

結論

技術的には可能そうです。

以下の記事で、サンプルコードを交えた実装例を取り上げます。

参考文献

Discussion