O hirunewani blog

ページを離れた際に呼ばれるブラウザイベントについて

Created at

ページを離れた際に呼ばれるブラウザイベントについて、共有したくなったので改めて少し調査した上でまとめた。

まとめ

ユーザーがページを離れた際にユーザーの動作をブロックしたいわけでなければvisibilitychangeが最適である。 特にモバイルでの動作を想定している場合、visibilitychangeはホーム画面への移動や画面OFF、別アプリの移動などでも呼ばれるため、ユーザーがページを離れたことを検知するのに適している。

ページの非表示ではなく、ページが実際にアンロードされることを検知したい場合はpagehideを利用した方が適している。

ユーザーの動作を止めたい場合はbeforeunloadを使うしかないが、iOS Safariでは利用できず、他のモバイル環境で完璧に動作するわけではない。またモバイルでなくても、ユーザーの操作がなければダイアログは表示されない。

unloadは廃止が計画されており利用すべきではない。

動作確認

以降の文章で調査・検証などの表現をしている場合、次のページをChrome 129、Edge 129、Safari 18、iOS Safari 18で動作確認をしている。

https://react-ts-itkkda.stackblitz.io/

このページを2つのタブで開き、片方でページを閉じるなどを行うともう片方にログが送信される仕組みになっている。

大雑把な仕組みは次のようになっている。

const channel = new BroadcastChannel("...");

useEffect(() => {
  const controller = new AbortController();
  channel.addEventListener("message", saveLogger, {
    signal: controller.signal,
  });
  document.addEventListener("visibilitychange", postLog, {
    signal: controller.signal,
  });
  window.addEventListener("pagehide", postLog, {
    signal: controller.signal,
  });
  window.addEventListener("beforeunload", posyLog, {
    signal: controller.signal,
  });
  return () => {
    controller.abort();
  };
}, []);

ページを離れた際に呼ばれるブラウザイベント

次のようなイベントが存在する。

  • beforeunload
  • pagehide
  • visibilitychange
  • unload

ページを閉じようとした場合、上から下の順にイベントが呼ばれる。

詳しくはPage Lifecycle APIを参照。

unloadイベント

利用すべきでない。

  • 非推奨かつ廃止が計画されているため利用すべきでない。
  • このイベントがリッスンされている場合、BFCacheが妨げられる。

beforeunloadイベント

iOS Safariで動作させたい場合は、機能しない。 積極的に使うべきではなく、ユーザーが入力途中の場合のみイベントリスナーを登録するようにすべき。

  • ドキュメントがアンロードされる直前に呼ばれる。
  • ページを閉じようとした際にダイアログを表示できる。
    • iOS Safariでは利用できない。
    • event.preventDefault()を呼ぶことで表示できるようになる。
    • event.returnValueに何らかの値を渡す必要はない。
      • 検証したところ、無くてもダイアログは表示された。
      • 以前はevent.returnValueに文字列を渡すことで任意のメッセージを表示できる環境が存在した。
      • 現在は、ブラウザ固有のメッセージを表示することしかできない。
      • 現在は、event.returnValue自体が非推奨になっている。
    • ユーザーの操作がない場合もダイアログは表示されない。
  • window.alert()window.confirm()などの実行を無視する。
  • iOS Safariで呼ばれていない。
    • Can I useを見ると、イベント自体はサポートしているがevent.preventDefault()による有効化をサポートしていないだけであるように見えるが、実際に検証したところ呼ばれていない。
    • ChromeやEdgeなどでは、event.preventDefault()を実行しなくとも呼ばれる。
  • このイベントがリッスンされている場合、BFCacheが妨げられる。
    • そのため、常にリッスンするような実装は避けるべき。

pagehideイベント

ページのアンロード時に何かを実行したい場合に適している。 しかし、beforeunloadと異なり、ユーザーが入力中に離脱することを防ぐような使い方は出来ない。

  • セッション履歴で現在のページを非表示にすると呼ばれる。
    • Chromeで検証したところ、beforeunloadの後に呼ばれていた。
  • unloadbeforeunloadのようにBFCacheなどブラウザのキャッシュが妨げられることはない。
  • このイベントはbeforeunloadとは異なり、イベントをキャンセルすることは出来ない。
  • window.alert()などの実行は無視される。
  • 以前Safariでvisibilitychangeにバグがあったため、pagehideを推奨する記事が多い。

visibilitychangeイベント

ユーザーのセッション終了を検知するイベントとしては最善としてMDNなどでは紹介されている。 beforeunloadと異なり、ユーザーが入力中に離脱することを防ぐような使い方は出来ない。

  • ページの表示非表示が切り替わった際に呼ばれる。
  • モバイルでは、ホーム画面への移動や画面OFF、別アプリの移動などでも呼ばれる。
  • document.visibilityStateまたはdocument.hiddenを利用してページの表示/非表示を識別できる。
  • window.alert()などの実行は無視されない。
    • ただし実行タイミングが異なり、beforeunloadのダイアログのような使い方は出来ない。
    • ページを閉じる場合は実行されず、タブを切り替えた場合は、タブを元に戻すとダイアログが表示される。
  • pagehideと同様に、イベントをキャンセルすることは出来ない。
  • pagehideと同様に、BFCacheなどブラウザのキャッシュが妨げられることはない。
  • iOS Safari 14.5以前では、visibilitychangeがページ遷移時に発火しないバグがあった。
    • そのためか、beforeunloadの代わりにpagehideを推奨する記事が多い。