お気に入りのサイトやアプリからの通知は、もはやスマートフォンだけの話ではありません。ブラウザーから通知を直接受け取ることも普通になってきました。たとえばFacebookは新しい友達リクエストが届いたり、誰かのコメント内で言及されたりすると通知を送ります。人気メッセージアプリのSlackは会話の中で話題になった人に通知を送ります。
私はフロントエンド開発者として、大量の情報を扱わないWebサイトでもこのようなブラウザーの通知(notification)を活用できるか興味がありました。Webサイト訪問者の興味に合った通知を実現するには、どうすれば良いのでしょうか。
この記事では「Concise CSS」のWebサイトに通知機能を追加して、Concise CSSの新バージョンがリリースされるたびに、サイト訪問者に通知を表示するようにします。例ではブラウザーのNotification APIとlocalStorageを使って実現する方法を解説します。
Notification APIの基本
最初に、サイト訪問者のWebブラウザーが通知をサポートしているかどうかを調べる必要があります。作業の対象はnotificationオブジェクトです。
(function() {
if ("Notification" in window) {
}
})();
上のコードでは、訪問者のブラウザーが通知に対応しているかどうかを調べただけです。次に、訪問者に対し「通知の許可を求める表示」ができるかどうかを調べます。
変数に、permissionプロパティの値を格納します。すでに承認または拒否されていたらなにも返りません。もし、以前に許可のリクエストをしていなければ、ここでrequestPermissionメソッドでユーザーに許可を求めます。
(function() {
if ("Notification" in window) {
var permission = Notification.permission;
if (permission === "denied" || permission === "granted") {
return;
}
Notification.requestPermission();
}
})();
Webブラウザーに、上の画像のようなダイアログが表示されます。
これで許可を求めたので、許可されたら通知を表示するようにコードを変更します。
(function() {
if ("Notification" in window) {
var permission = Notification.permission;
if (permission === "denied" || permission === "granted") {
return;
}
Notification
.requestPermission()
.then(function() {
var notification = new Notification("Hello, world!");
});
}
})();
おもしろくはありませんが、ちゃんと動きます。
例では許可されたあとに通知を表示するため、requestPermission()メソッドのプロミスベースの構文を使いました。通知はnotificationコンストラクタを使って表示します。このコンストラクタは2つのパラメータがあります。1つは通知のタイトル、他方はオプションです。渡せるオプションパラメータの一覧は、公式ドキュメントを参照してください。
フレームワークのバージョンを保存する
記事の冒頭で、通知表示の補助のためlocalStorageを使うと書きました。JavaScriptでクライアント側の持続的な情報を保存するなら、localStorageはぴったりの方法です。訪問した時点の最新バージョン番号(例:1.0.0)を保管するため、conciseVersionという名でlocalStorageのキーを作ります。フレームワークの新しいバージョンのチェックにこのキーを使います。
conciseVersionキーの値を、フレームワークの最新バージョンの番号にどうやって更新すれば良いのでしょうか。最初にWebサイトを訪れたその時点の最新バージョンの番号を、保存しておく手段が必要です。次に、新バージョンがリリースされたときに値を更新する必要があります。conciseVersionの値が変更されるたびに、サイト訪問者に通知を表示してフレームワークの新バージョンを知らせなければなりません。
そのために、Webページに隠し要素を加えます。この要素はjs-currentVersionというクラス名で、フレームワークの最新バージョン番号だけを入れておきます。要素はDOMにあるので、JavaScriptから簡単に読み書きできます。
この隠し要素は、conciseVersionキーに入っているフレームワークのバージョン番号の保管に使います。また、新バージョンがリリースされたときのキー更新にも使います。
<span class="js-currentVersion" aria-hidden="true">3.4.0</span>
ちょっとしたCSSで非表示にできます。
[aria-hidden="true"] {
display: none;
visibility: hidden;
}
注:この隠し要素は意味のあるコンテンツを一切含まないので、ページ訪問者がアクセスする必要はありません。要素を非表示にする方法として、aria-hidden属性をtrueにし、display: noneとしました。コンテンツを隠す方法の詳細はWebAIMの記事を参照してください。
要素をJavaScriptで操作できるようになりました。次に、いま作った隠し要素内のテキストを返す関数が必要です。
function checkVersion() {
var latestVersion = document.querySelector(".js-currentVersion").textContent;
}
この関数は、textContentプロパティを使って.js-currentVersion要素の中身(最新バージョン番号)を取り出して変数へ保存します。次に、localStorageのキーconciseVersion(前回訪問時点の最新バージョン番号)の中身を取り出して保存するために別の変数を用意します。
function checkVersion() {
var latestVersion = document.querySelector(".js-currentVersion").textContent;
var currentVersion = localStorage.getItem("conciseVersion");
}
これでフレームワークの最新バージョン番号が変数(latestVersion)に入りましたし、localStorageキーの値も別の変数(currentVersion)に入りました。
最初にconciseVersionキーが存在するかをチェックします。もし無ければ、初めての訪問なので通知を表示します。conciseVersionキーがあったら、その値(currentVersion変数の中身)と現在の最新バージョン(latestVersion変数の中身)とを比較します。訪問したときのバージョン(latestVersion)が前回訪問時のバージョン(currentVersion)よりも新しければ、新バージョンがリリースされたことが分かります。
注:2つのバージョン番号(文字列型)の比較にはsemver-compareライブラリーを使います。
以上を念頭に、サイト訪問者に通知を表示し、conciseVersionキーを現在の最新バージョンの値に更新します。
function checkVersion() {
var latestVersion = document.querySelector(".js-currentVersion").textContent;
var currentVersion = localStorage.getItem("conciseVersion");
if (currentVersion === null || semverCompare(currentVersion, latestVersion) === -1) {
var notification = new Notification("Hello, world!");
localStorage.setItem("conciseVersion", latestVersion);
}
}
この関数を使うには、許可を求めるコードを以下のように変更します。
(function() {
if ("Notification" in window) {
var permission = Notification.permission;
if (permission === "denied") {
return;
} else if (permission === "granted") {
return checkVersion();
}
Notification.requestPermission().then(function() {
checkVersion();
});
}
})();
これで、ユーザーが通知を以前許可したか、もしくはいま許可すれば、通知を表示できます。
通知を表示する
これまではユーザーに対し、それほど情報がない単純な通知だけを表示しました。例では、その場でブラウザー通知を表示する関数を書いて、通知のいろいろな機能を使います。
下の関数は、任意のリンク先や通知の持続時間とともに、本文テキストやタイトルを設定するパラメーターがあります。この関数で、通知の本文とアイコンを保存するoptionsオブジェクトを作ります。また新規にNotificationオブジェクトのインスタンスも生成し、通知のタイトルtitleと、オプションの値を持ったoptionsオブジェクトをわたします。
次に、もし通知にリンクを貼るならイベントハンドラonclickを加えます。指定した一定時間経過後に通知を閉じるためにはsetTimeout()を使います。関数が呼ばれたときに時間が指定されていなければ、デフォルト値は5秒です。
function displayNotification(body, icon, title, link, duration) {
link = link || null; // Link is optional
duration = duration || 5000; // Default duration is 5 seconds
var options = {
body: body,
icon: icon
};
var n = new Notification(title, options);
if (link) {
n.onclick = function () {
window.open(link);
};
}
setTimeout(n.close.bind(n), duration);
}
ここで先のcheckVersion()関数を改造して、もっと情報量のある通知をユーザーに表示します。
function checkVersion() {
var latestVersion = document.querySelector(".js-currentVersion").textContent;
var currentVersion = localStorage.getItem("conciseVersion");
if (currentVersion === null || semverCompare(currentVersion, latestVersion) === -1) {
displayNotification(
`Click to see what's new in v${latestVersion}`,
"http://concisecss.com/images/logo.png",
"A new version of Concise is available",
`https://github.com/ConciseCSS/concise.css/releases/v${latestVersion}`
);
localStorage.setItem("conciseVersion", latestVersion);
}
}
例では通知に説明文、画像、タイトル、リンクを表示させるためにdisplayNotification関数を使っています。
注:文字列の中に「式」を埋め込むために、ES6のテンプレートリテラルを使用しています。
コードの完成とテストの実施
以下に、記事で使用したコード全体があります。
このコードを実行するとブラウザーに次のような通知が表示されます。
テストをするには、使用するブラウザーの通知の許可設定について知っておく必要があります。Google Chrome、Safari、Firefox、Microsoft Edgeの通知の管理に関するクイックリファレンスへリンクを貼っておきます。また、テストのためにlocalStorageの値を消したり変更したりするには、ブラウザーの開発者用コンソールに慣れておきましょう。
テストとしてスクリプトを1回限りで実行し、HTML要素js-currentVersionクラスの値を変更しながら、処理の違いを確認しましょう。また、バージョン番号を同じにして再度実行し、出ないはずの通知が表示されないかを確認します。
さらに先へ
動的なブラウザー通知の方法はこれだけです。もし、さらに柔軟なブラウザー通知が必要なら、Service Worker APIの学習をおすすめします。Service Workerなら、ユーザーがサイトを訪れていなくても通知をプッシュ送信で知らせて、さらにタイムリーな更新が実現できます。
※本記事はJulian Motzが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。
(原文:Displaying Dynamic Messages Using the Web Notification API)
[翻訳:西尾健史/編集:Livit]