beforeunloadのページ離脱はブラウザによって挙動が違うので注意が必要

beforeunloadイベントとは

Webサイトを開発しているとフォームの入力中にユーザーが誤ってページを閉じたり、別のページに移動しようとしたりするのを防ぎたい場面がよくあります。

そんなときに便利なのがJavaScriptのbeforeunloadイベントですが、ブラウザ(Chrome, Firefox, Safariなど)によって、挙動や仕様が大きく異なるため実装には注意が必要です。

基本的な実装方法

現代のブラウザでは、イベントオブジェクトのpreventDefault()を呼び出すことで、離脱確認ダイアログを表示させることができます。

JavaScript
window.addEventListener('beforeunload', (event) => {
  const hasNickname = !!document.getElementById('nickname')?.value

  if (!hasNickname) return
  event.preventDefault()
})

この記事には上記のスクリプトが含まれているので、以下のテキストフィールドに入力してブラウザをリロードしてみてください。

テキストフィールドに文字を入力した状態でリロードすると「このサイトを再読み込みしますか?」という確認ダイアログが表示されて、「再読み込み」を選択しなければリロードできなくなっていることが確認できます。

beforeunloadのページ離脱はブラウザによって挙動が違うので注意が必要

event.returnValue = '' は不要

昔は event.returnValue = '[任意のテキスト]' で確認ダイアログのテキストの内容を変更できましたが、現在はできなくなっています。

生成AIで出力されるコードだと、「event.returnValue = ''」を追加していることが多いですが、現代のブラウザでは不要です。

メッセージがブラウザによって異なる

ChromeとEdgeではリロードした場合は「このサイトを再読み込みしますか?」と表示されますが、Safariだと「このページから移動してもよろしいですか?」になります。

Safari beforeunloadイベントの確認ダイアログ

ここのメッセージは開発者側では変更できず、ブラウザによって内容が異なるので注意が必要です。

Firefoxだと「このページから移動しますか? 入力した情報は保存されません。」と表示されるのですが、Cookieやローカルストレージなどを使用すれば入力情報を保存できるので、「保存されません。」というメッセージを変更できないのは厄介です。

Firefox beforeunloadイベントの確認ダイアログ

Safariは「戻る」で確認ダイアログが表示されない

Chrome, Edge, Firefoxなどのブラウザでは「戻る(←)」または「進む(→)」を押したときでも、beforeunloadイベントの離脱防止のための確認ダイアログを表示できますが、Safariだと「戻る(←)」または「進む(→)」押したときの離脱時には確認ダイアログが表示されません。

そのため、もしクライアントに「戻る(←)」を押したときにページから離脱する際は確認ダイアログを表示するよう実装してほしいという要望があっても、Safariではできないことを伝える必要があります。

ReactやVueでは必ずアンマウントする

ReactやVueではコンポーネントで使用するケースが多いので、beforeunloadイベントを使用するコードを書いたらクリーンアップ関数またはアンマウントによる解除処理も必ず書いてください。

これを書かないとコンポーネントが除去されたあともイベントが残り続けるため、無駄なメモリ消費やイベントが二重・三重で発生するバグを含めてしまいます。

NicknameForm.tsx
import { useState, useEffect } from 'react'

export default function NicknameForm() {
  const [nickname, setNickname] = useState('')

  useEffect(() => {
    const handleBeforeUnload = (event) => {
      if (!nickname) return
      event.preventDefault()
    }

    window.addEventListener('beforeunload', handleBeforeUnload)
    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload)
    }
  }, [nickname])

  return (
    <input
      type="text"
      value={nickname}
      onChange={(e) => setNickname(e.target.value)}
      placeholder="ニックネームを入力"
    />
  )
}
NicknameForm.vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const nickname = ref('')
const handleBeforeUnload = (event) => {
  if (!nickname.value) return
  event.preventDefault()
}

onMounted(() => {
  window.addEventListener('beforeunload', handleBeforeUnload)
})

onUnmounted(() => {
  window.removeEventListener('beforeunload', handleBeforeUnload)
})
</script>

<template>
  <input
    v-model="nickname"
    type="text"
    placeholder="ニックネームを入力"
  />
</template>

まとめ

beforeunloadイベントを使用すると、フォーム入力中などにユーザーが誤ってページを離脱するのを防ぐことができます。

しかし、Chrome、Edge、Firefox、Safariでは挙動や表示されるメッセージが異なり、特にSafariでは「戻る」「進む」による離脱時に確認ダイアログが表示されないなどの制約があります。

ReactやVueなどのフレームワークで利用する場合は、コンポーネントのアンマウント時にイベントリスナーを解除する処理も忘れずに実装しましょう。

beforeunloadイベントは便利な機能ですが、ブラウザごとの仕様差異を理解したうえで実装することが重要です。