Skip to content

Page lifecycle

Bridges the browser page's foreground/background lifecycle to the engine: pauses the loop when the tab or app goes to the background and resumes on return, emitting the elapsed time so offline progress can credit it. The browser-coupled half that keeps the core headless.

ts
import { pageLifecycle } from "@idlekitjs/browser/page-lifecycle";

Usage

ts
engine.use(pageLifecycle());

No options. From then on:

  • tab hidden → engine.pause() — frames stop, no battery burn, no throttled half-simulation;
  • tab visible → emits resume with the elapsed background milliseconds, then engine.resume() — and the loop's first frame back has a zero delta, so there is no time jump.

The implementation listens to the Page Visibility API (visibilitychange / document.hidden). The name describes the bridge's responsibility: page foreground/background lifecycle into engine pause/resume.

Why opt-in?

Pausing on hidden tabs is policy, not physics. A game with a server clock, a music app, or a test harness may want the loop running. Core therefore only exposes pause()/resume(); when to call them is this bridge's opinion, and you opt into it. This is also what keeps document listeners out of @idlekitjs/core entirely.

The resume event

The elapsed time is broadcast on the engine bus:

ts
engine.events.on("resume", (elapsedMs) => {
  console.log(`Welcome back! You were away ${Math.round(elapsedMs / 1000)}s`);
});

offline-progress is the standard consumer, but your UI can listen too (a "welcome back" toast pairs well with the credit).

SSR / headless safety

In environments without document (Node, SSR, tests) the bridge is a no-opengine.use(pageLifecycle()) is always safe, so shared wiring code needs no environment branches.

Cleanup

The bridge registers exactly one visibilitychange listener in setup and removes it in teardownengine.dispose() leaves nothing behind.

Common pitfalls

  • Installing offline-progress without page-lifecycle — same-session backgrounding won't be credited (only reloads). Install both.
  • Pausing manually on top of it — the loop's pause is idempotent but your bookkeeping may not be; let the bridge own tab-driven pausing.