JOURNAL

スマホの『バッテリーセーバー』が、Webアプリの動作にどう影響するか

「スマホでWebタイマーをかけていたのに、気づいたら時間が数分ズレていた」「バックグラウンドで動かしていたWebアプリが、いつの間にか止まっていた」——そんな経験はありませんか?

赤猫座の道具店で公開している複数タイマー「SPLIT TIMER」でも、過去にご利用者から「たまにタイマーのカウントが微妙に遅れることがある」というご報告をいただいたことがありました。原因を徹底的に調査した結果、犯人として浮かび上がってきたのが、iOSの「低電力モード」やAndroidの「バッテリーセーバー」でした。

スマホのバッテリーを長持ちさせるための親切機能が、実はWebブラウザの裏側でどのような「制限」をかけているのか。今回は、低電力モードがWebアプリに与える影響と、フロントエンド開発者が知っておくべき技術的な落としどころについて、詳しく解説してみます。

この記事の要点

低電力モードが有効になると、ブラウザ内のタイマー処理(setTimeout等)の実行頻度が極端に間引かれ、画面消灯が早まり、通信も抑制されます。タイマーが遅れる根本原因(相対時間 vs 絶対時間)や、Web標準API(Screen Wake Lock等)を駆使した対策をまとめました。

1. 低電力モード発動時にブラウザ内部で起きる「4大異変」

スマホがバッテリーセーバー状態に入ると、OSは電力消費を抑えるためにハードウェアとソフトウェアの両面から強烈なブレーキをかけます。Webブラウザ(SafariやChrome)の内部では、主に以下の4つの現象が発生します。

① タイマー処理(setTimeout / setInterval)の極端な間引き(Throttling)

JavaScriptで時間を計る際によく使われる setTimeout や setInterval ですが、通常時でも非アクティブなタブでは1秒間隔程度に制限(スロットリング)されます。しかし低電力モード下では、この制限がさらに厳格化され、数秒〜数十秒に1回しか処理が回ってこない、あるいは完全に凍結(フリーズ)される状態に陥ります。

② 画面自動消灯(スリープ)までの時間が最短化される

普段なら数分間点灯し続ける設定にしていても、低電力モードがONになった瞬間、OSの設定に関わらず30秒程度で強制的に画面が真っ暗になります。画面が消灯すると、ブラウザは「バックグラウンド状態」へ移行するため、後述の間引き処理が一気に加速します。

③ バックグラウンド通信・ポーリングの制限

裏でサーバーと通信し続けるFetch処理やWebSocket接続、定期的なデータ同期などがOSによって一時停止・切断されます。これにより、サーバーからのリアルタイム通知や更新の受信が大幅に遅延します。

④ CPUクロック低下によるフレームレート(fps)の制限

CPUの動作クロックが強制的に下げられ、ProMotion(120Hzディスプレイ)搭載機でも上限が60Hzや30Hzに引き下げられます。CSSアニメーションやCanvas描画がカクつきやすくなるのもこのためです。

2. なぜタイマーアプリは「狂う」のか? (相対時間 vs 絶対時間の罠)

Web制作の入門書などでよく見かけるタイマーの実装例に、以下のようなコードがあります。

// ❌ 危険な実装例:1秒ごとにカウントを1増やす(相対時間カウント)
let seconds = 0;
setInterval(() => {
  seconds++;
  updateDisplay(seconds);
}, 1000);

この書き方は、「ブラウザが1秒(1000ms)ぴったりに毎回正確に関数を呼んでくれる」という誤った前提に基づいています。

低電力モードで画面が消えたり裏に回ったりすると、OSは平気で「5秒に1回」しかこの関数を実行してくれません。その結果、「現実世界では5秒経過したのに、プログラム上は1回しか呼ばれていないので1秒しか進んでいない」という致命的な時間ズレが発生します。

💡 開発者の基本鉄則:常に「現在時刻(絶対時間)」との差分を取る

時間を扱うツールでは、呼び出し回数を数えるのではなく、開始時のタイムスタンプ(Date.now() や performance.now())を記録しておき、「現在時刻 - 開始時刻」の差分を都度計算する(絶対時間方式)のが鉄則です。

// ⭕ 正しい実装例:開始時刻との差分を計算する(絶対時間方式)
const startTime = Date.now();
setInterval(() => {
  const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
  updateDisplay(elapsedTime);
}, 250);

この書き方であれば、途中で処理が何秒間間引かれようとも、次に動いた瞬間に「現実の正確な経過時間」へ一瞬で補正されます。

3. 比較表:通常モード vs 低電力モード時のブラウザ挙動

挙動・機能 ⚡ 通常モード 🪫 低電力モード(バッテリーセーバー)
タイマー精度(フォアグラウンド) ミリ秒単位で高精度 概ね正常(負荷により稀に遅延)
タイマー精度(バックグラウンド) 約1秒間隔でスロットリング 数秒〜数十秒単位で間引き / ほぼ停止
画面消灯(自動スリープ) ユーザー指定の時間(1〜5分など) 30秒前後で強制消灯
Web Notifications(ブラウザ通知) 指定時刻に即時配信 OS制御により数分遅れて届く場合あり
画面描画(フレームレート) 60fps〜120fpsで滑らか 30fps〜60fpsに制限(カクつきやすい)
Web Workerの動作 バックグラウンドでも比較的安定 タブ停止時に巻き添えで凍結される

4. フロントエンドができる「4つの対抗策」と技術的落としどころ

OSレベルの電力制御をWebブラウザ側から完全に無効化することはできませんが、フロントエンドの工夫次第で影響を最小限に抑えることは可能です。

① Screen Wake Lock API で画面の消灯を防ぐ

ユーザーが画面を見ながら作業するタイマーやストップウォッチでは、Screen Wake Lock API を使って「計測中は画面をスリープさせない」ようにリクエストするのが極めて有効です。

// 画面の自動消灯を防止する
let wakeLock = null;
async function requestWakeLock() {
  try {
    wakeLock = await navigator.wakeLock.request('screen');
  } catch (err) {
    console.warn('Wake Lock request failed:', err);
  }
}

② Page Visibility API で復帰時に一瞬で画面を再同期する

ユーザーが別のアプリからブラウザに戻ってきた瞬間(visibilitychange イベント発火時)に、現在時刻との差分を再計算して画面上の数字やプログレスバーを即座に最新状態へ更新します。「戻ってきたら数字がワープして現在時刻に追いつく」挙動を作ることで、見た目上の破綻を防ぎます。

③ Web Worker を活用したタイマーの補助

メインスレッドで重いDOM操作やアニメーションが走っていてもタイマーが影響を受けないよう、バックグラウンドスレッド(Web Worker)でインターバルを刻むアプローチも有効です(ただし、OSがブラウザタブ自体を丸ごとサスペンドした場合はWorkerも停止します)。

④ 親切なUIメッセージ(マイクロコピー)を添える

どれほどコード側で対策しても、スマホの画面を伏せてポケットに入れた状態で15分後にWebブラウザから音を鳴らすのは、現代のモバイルOSのセキュリティ・省電力仕様上、ネイティブアプリに比べてどうしても分が悪いです。
そのため、「※画面を閉じるとタイマーが遅れる場合があります。長時間の計測時は画面を開いたままご利用ください」といった注記をそっと添えておくことが、結果的にユーザーの信頼につながります。

まとめ:OSの省電力機能と上手に付き合うWebアプリ作り

モバイルOSがバッテリーセーバーでブラウザを厳しく縛るのは、すべて「ユーザーのスマホの電池を1分でも長持ちさせるため」という正義に基づいています。

開発者としては「思い通りに動いてくれない!」と頭を抱えたくなる瞬間もありますが、その制約を理解した上で、絶対時間の活用やScreen Wake Lock APIを組み合わせ、「限られた環境下で最もスマートに動く落としどころ」を探っていくことこそが、Web道具作りの面白さでもあります。

赤猫座の道具店でも、スマホの厳しい省電力事情と日々仲良く喧嘩しながら、手元で心地よく動く道具たちを磨き続けていきます。😽