「タイマーが終了した瞬間に、スマホの画面にポンと通知を出したい」
タイマーやアラーム系のツールを作っていると、ごく自然にこの要望が出てきます。そして意気揚々とNotifications APIを実装し、意気揚々と実機テストをした瞬間……「あれ? 通知、届くときと届かないときがあるな……?」なんで?となります。
赤猫座の道具(SPLIT TIMERなど)を開発していたとき、まさにこの「Web通知の信頼性問題」に直面し、夜な夜なスマホを睨みつけながら丸2日溶かしました。今回は、Webブラウザの通知がスマホで届かない理由と、最終的にたどり着いた現実的な解決策を詳しくまとめます。
1. モバイル通知を阻む「3大トラップ」
① iOS Safariの「ホーム画面追加」という高すぎる壁
長年待ち望まれ、iOS 16.4からようやくiPhoneでもWeb Push通知が使えるようになりました。しかし、ここには開発者を絶望させる大きな前提条件があります。
それは、「Safariのブラウザタブで開いているだけでは通知許可が取れず、ユーザーがわざわざ『ホーム画面に追加(PWA化)』した状態で起動しないと動かない」という仕様です。
Webツールを「開いてすぐ手軽に使いたい」と思っているライトユーザーに対して、「まず共有ボタンを押して、下にスクロールしてホーム画面に追加し、そこから開き直してください」と要求するのは、あまりにもハードルが高すぎます。
② Notifications APIの「tag重複」で通知が握りつぶされる怪現象
AndroidやPCでテストしていた際、「1回目のタイマーは鳴るのに、2回目や短時間のタイマーがなぜか無言でスルーされる」という謎のバグに半日悩まされました。
犯人は、通知オプションに指定していた tag 属性でした。
// 危険な例:同じtagを使い回すと古い通知に上書き・抑制される
new Notification('1分経過しました!', {
body: '次のラップに進みます。',
tag: 'split-timer-alert' // ← 同じtagだとOSに「重複」とみなされて消える!
});
// 安全な例:毎回ユニークなIDを付与するか、tagを省略する
new Notification('1分経過しました!', {
body: '次のラップに進みます。',
tag: 'split-timer-' + Date.now()
});
OSやブラウザは、画面が通知で埋め尽くされないように「同じtagの通知が来たら前の通知を上書き(または静かに破棄)」します。これを知らずに固定文字列を渡していたため、短時間の連続タイマーが次々と握りつぶされていたのでした。
③ 一度「拒否」されたら二度と出せないワンチャン勝負
Web通知の許可ダイアログは、ユーザーが一度「ブロック(許可しない)」を押してしまうと、JavaScript側からは二度とダイアログを再表示できません。ユーザー自身にブラウザの深い設定画面を開いてもらい、手動で権限をリセットしてもらわない限り永遠に沈黙します。
いきなり Notification.requestPermission() を呼ぶと高確率で拒否されるため、事前に「通知を許可するとタイマー終了時にお知らせできます」という丁寧な自前モーダル(ソフトプロンプト)を挟むなどの工夫が必須になります。
2. 最終結論:通知を当てにせず「音(Web Audio API)」をメインに据える
コード側でできる限りの対策を施しても、最終的にはOSの省電力機能やスリープ状態、ユーザーの権限設定という「Webの外側の事情」に左右されます。時間を計るツールにおいて、「鳴るかもしれないし鳴らないかもしれない」というギャンブル設計は致命的です。
そこで赤猫座のSPLIT TIMERでは、以下のように割り切った設計にシフトしました。
- 画面通知(Notification):許可されたらラッキー程度の「サブのおまけ」に格下げ。
- 音・ビープ音(Web Audio API):通知許可がなくても確実に鳴らせる「主役の通知手段」に昇格。
// Web Audio APIなら事前の権限許可なしでクリアな警告音を鳴らせる
function playBeep(frequency = 880, duration = 0.2) {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.value = frequency;
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
gain.gain.exponentialRampToValueAtTime(0.00001, audioCtx.currentTime + duration);
osc.stop(audioCtx.currentTime + duration);
}
Web Audio APIによる電子音(ビープ音)であれば、重い音声ファイルをロードする必要もなく、通知権限の許可をユーザーに乞う必要もありません(※最初の画面タップ時にAudioContextを一度アンロックしておく作法だけ必要です)。
| 通知手段 | 到達の確実性 | ユーザーの手間・心理的負担 | 総評・おすすめ度 |
|---|---|---|---|
| Web Push通知(iOS) | △(PWA追加時のみ) | ✕(ホーム画面追加が必須で超面倒) | △ Webアプリ化前提ならあり |
| Notifications API | ○(Android/PCなら良好) | △(拒否されたら即終了の罠) | ○ 補助的な通知として併用 |
| Web Audio API(ビープ音) | ◎(タブが生きていれば100%) | ◎(権限許可不要で手軽) | ★ タイマー系ツールの本命 |
| 画面フラッシュ・アニメーション | ◎(見ている間は100%) | ◎(完全自動で邪魔にならない) | ★ 視覚補助として必須 |
完璧を目指すより「確実に伝わる手段」を選ぶ
最新のWeb APIを全部盛りにして「どんな環境でも完璧にPush通知を飛ばすぞ!」と意気込むのは技術者として楽しいものですが、OSのセキュリティや省電力の壁を前にすると、時にシンプルな「音」や「画面の点滅」の方が圧倒的に頼りになります。
「道具として本当にユーザーを裏切らない手段はどれか?」を突き詰めた結果としての音メイン設計。丸2日悩んで出したこの結論は、今でも間違っていなかったなと思っています。
