【JavaScript】画面遷移時にビーコンをサーバーに送信する Beacon API

[2023-12-02 更新]

ウェブページやウェブアプリケーションからサーバーに対して何かしらのイベント情報を送信したいと考えたことはないでしょうか。それ自体は簡単時に実現可能なのですが、ページがアンロードされるタイミングでサーバーに情報を送りたい場合は、実は簡単ではありません。確実にイベントを送信できるか、利用者に対してパフォーマンスを悪化させないか、など課題があります。

ページアンロード時に確実に、かつ、ユーザー体験を損なわずにサーバーにイベントを送信するのが Beacon API です。今回は、この Beacon API が必要となった背景と、Beacon API の使い方について解説します。

Beacon API が必要となった背景

ユーザーがページを離れる際、つまり、ページがアンロードされる際に、何かしらの情報をサーバーに送信したい、というニーズは昔から存在していました。その多くは、ウェブサイトのアクセス解析が目的と思われますが、その必要性の理由は様々です。

ユーザーがいつページを離れるのかは予想できません。そのため、window オブジェクトで発生する beforeunload イベントや unload イベントの発生をトリガーに次のような手法が使われてきました。

  • img 要素を生成して src 属性をセットする
  • XMLHttpRequest を非同期モードではなく同期モードで実行する

img 要素を生成する方法は、画像ファイルのダウンロードのリクエストが確実に行われる保証はありません。

window.addEventListener('beforeunload', () => {
    const img = document.createElement('img');
    img.src = 'beacon.cgi?tm=' + Date.now();
}, false);

同期モードの XMLHttpRequest はユーザー体験という視点で見ると、推奨できるものではありません。というのも、もし HTTP リクエストの応答が遅いと、ページ遷移が待たされてしまうからです。

window.addEventListener('beforeunload', () => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", 'beacon.cgi?tm=' + Date.now(), false);
    xhr.send();
}, false);

なお、同期モードの XMLHttpRequest は、現在は、事実上、使えないでしょう。Chrome や Edge のような Chromium 系ブラウザーでは、ページアンロードの際の同期モードの XMLHttpRequest 実行は無効になり、実行しようとするとエラーになります。Safari はエラーにはならないものの、結果的に XMLHttpRequest は機能しません。現在では唯一 Firefox のみが機能します。

navigator.sendBeacon() メソッド

Beacon API が提供するのは navigator.sendBeacon() メソッドのみです。このメソッドは POST メソッドの HTTP リクエストをブラウザーに予約します。

sendBeacon() メソッドは 2 つの引数を取ります。 第一引数にはリクエスト URL を、第二引数にはサーバーに送信するデータを指定します。第二引数はオプションで指定がなければ null が指定されたとして処理されます。

第二引数の送信データには、BlobBufferSourceFormDataURLSearchParams オブジェクト、または、文字列のいずれかを指定することができます。次のサンプルコードは、FormData オブジェクトを第二引数に指定しています。

const fd = new FormData();
fd.append('tm', Date.now());
navigator.sendBeacon('beacon.cgi', fd);

次のサンプルコードは、URLSearchParams オブジェクトを第二引数に指定しています。

const params = new URLSearchParams({ tm: Date.now() });
navigator.sendBeacon('beacon.cgi', params);

sendBeacon() メソッドは、あくまでもブラウザーに対して HTTP リクエストを予約するだけです。実際に送信に成功したか、そして、どんなレスポンスがサーバーから返ってきたのかに関しては関与しません。

sendBeacon() メソッドを使うタイミング

Beacon API はページ遷移のタイミングで使いたい場合が多いわけですが、beforeunload イベントや unload イベントの発生をトリガーにしたいと考えるでしょう。

window.addEventListener('beforeunload', () => {
    const params = new URLSearchParams({ tm: Date.now() })
    navigator.sendBeacon('beacon.cgi', params);
}, false);

PC だけであれば、上記コードで問題ありません。ブラウザーを閉じたときですら、sendBeacon() メソッドは実行され、HTTP リクエストが送信されます。

しかし、スマートフォンが普及した現在、なんと日本国内でスマートフォンシェアが大きい iOS の Safari では HTTP リクエストが送信されません。sendBeacon() メソッドが実行されているのかどうかすら分かりませんが、いずれにせよ、HTTP リクエストが送信されるまでに至りません。

W3C の仕様書でも unload イベントの利用を推奨していません。また、MDN の説明も同様です。代わりに visibilitychange イベントを使うことを推奨しています。このイベントはブラウザーのタブのコンテンツの表示状態が変化したときに document オブジェクトで発生します。

document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') {
        const params = new URLSearchParams({ tm: Date.now() })
        navigator.sendBeacon('beacon.cgi', params);
    }
}, false);

このコードであれば、iOS Safari でページを遷移したときはもちろんのこと、タブを切り替えたとき、別のアプリに切り替えたとき、アプリを閉じた(強制終了ではなくホーム画面に戻る)ときでも sendBeacon() メソッドが実行されます。そして、この挙動は、Android の Chrome に加え、PC のブラウザーでも同じ挙動になります。

唯一、iOS Safari が他のブラウザーと違う点を挙げると、iOS Safari ではアプリを強制終了した場合、sendBeacon() メソッドが呼び出されません。PC のブラウザーはもちろんのこと、Android Chrome もアプリを強制終了したときには sendBeacon() メソッドは呼びだされます。

まとめ

iOS Safari だけの特殊な挙動のため面倒が増えてしまいましたが、sendBeacon() メソッドは画面遷移の際のユーザー体験を損なわない点が魅力的です。もしサーバーにビーコンを送信したい場合には積極的に使うと良いでしょう。また、画面遷移だけでなく、ウェブアプリ表示中でも、何かしらのイベント情報をサーバーに送信し、そのレスポンスを必要としない状況なら、sendBeacon() メソッドは便利でしょう。

今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。

Share