バックグラウンド処理

Background Sync(オフライン送信の後送)とPeriodic Background Sync(定期更新)。 どちらも Chromium 系のみ対応。Safari は未対応。

Background Sync

ユーザーがオフラインで送信したアクションを、オンラインに復帰した時に SW が裏で送る。 タブが閉じていても OS のバックグラウンド機構で動く。

登録

// メイン側
async function queueOutbox() {
  const reg = await navigator.serviceWorker.ready
  if ("sync" in reg) {
    await reg.sync.register("flush-outbox")
  } else {
    flushNow()    // フォールバック
  }
}

SW 側で受け取る

self.addEventListener("sync", (e) => {
  if (e.tag === "flush-outbox") {
    e.waitUntil(flushOutboxFromIDB())
  }
})

async function flushOutboxFromIDB() {
  const items = await db.getAll("outbox")
  for (const item of items) {
    try {
      await fetch("/api/save", {
        method: "POST",
        body: JSON.stringify(item.data),
      })
      await db.delete("outbox", item.id)
    } catch {
      throw new Error("retry later")    // sync が再登録される
    }
  }
}

挙動の特徴

Periodic Background Sync

定期的にバックグラウンドで動く。Chromium のみ。navigator.permissions で許可申請が必要で、 ユーザーがアプリをよく使う頻度に応じてOS が間隔を決める(最短12時間程度)。

// 許可確認
const status = await navigator.permissions.query({
  name: "periodic-background-sync",
})

if (status.state === "granted") {
  const reg = await navigator.serviceWorker.ready
  await reg.periodicSync.register("update-content", {
    minInterval: 24 * 60 * 60 * 1000,    // 24時間(最短のヒント)
  })
}
// SW 側
self.addEventListener("periodicsync", (e) => {
  if (e.tag === "update-content") {
    e.waitUntil(updateContent())
  }
})

ニュースアプリの記事先読み、メールのバックグラウンド受信などに。 制約が多いのでメインの仕組みにせず補助的に使う。

Background Fetch

大きなファイルのダウンロード(動画・モデルファイル等)をバックグラウンドで継続させる仕組み。 OS のダウンロードマネージャに似た UI が出る。Chromium のみ。

const reg = await navigator.serviceWorker.ready
const bgFetch = await reg.backgroundFetch.fetch(
  "movie-download",
  ["/movie/01.mp4", "/movie/01-subs.vtt"],
  {
    title: "映画01",
    icons: [{ src: "/icon.png", sizes: "192x192", type: "image/png" }],
    downloadTotal: 60 * 1024 * 1024,
  },
)
// SW 側
self.addEventListener("backgroundfetchsuccess", async (e) => {
  const cache = await caches.open("movies")
  for (const record of e.registration.matchAll()) {
    const response = await record.responseReady
    await cache.put(record.request, response)
  }
})

Wake Lock API(補助)

画面をスリープさせない仕組み。動画再生・地図ナビ等で使う。

let lock = null
async function keepAwake() {
  try {
    lock = await navigator.wakeLock.request("screen")
    lock.addEventListener("release", () => console.log("released"))
  } catch (e) {
    console.error(e)
  }
}

// 解除
await lock?.release()

Page Visibility

タブがバックグラウンドにあるかを検知。

document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden") {
    pauseAnimations()
  } else {
    resumeAnimations()
  }
})

BroadcastChannel

同じ origin の複数タブ・SW 間でメッセージ。同期処理に。

const channel = new BroadcastChannel("app")
channel.postMessage({ type: "DATA_UPDATED", payload: ... })

// 別のタブ / SW
channel.addEventListener("message", (e) => { ... })

Notification Triggers(実験的)

将来的にローカルでスケジュール可能な通知が来る予定(時刻指定で SW 起動)。 現時点ではサポートが限定的。

使い分け

やりたいこと使う API
オフライン中の送信を後送Background Sync
定期的にコンテンツ更新Periodic Background Sync
大ファイルのダウンロードBackground Fetch
サーバから即時通知Web Push
スリープさせないWake Lock
タブ間通信BroadcastChannel
対応状況

Chromium(Chrome / Edge / Brave / Samsung Internet)が最も進んでいる。
Safari は Sync 系・Background Fetch を未対応
Firefox も限定的。
PWA を Safari 中心で考えるなら、これらに依存しない設計を。