Glossary

Page Visibility API

Page Visibility API is a browser API that reports whether a page is visible to the user through document.visibilityState and the visibilitychange event. It has only two states, "visible" and "hidden". It is now specified in the HTML Standard, because the standalone W3C Page Visibility Level 2 document was discontinued on 23 June 2022.

How it works

The visibility state belongs to the top-level document. The spec says the document is "hidden" when it cannot be seen on any screen. Its examples are a background tab, a minimized browser, a browser moved off the screen, and an operating system lock screen covering the browser. The state is "visible" when any part of the viewport contents can be seen. A browser may also report "visible" while assistive technology is attached, even if the window would otherwise count as hidden.

A new Document starts with the state "hidden". The API has three members:

  • document.visibilityState returns "visible" or "hidden".
  • document.hidden returns true when the state is "hidden", and false otherwise.
  • The visibilitychange event fires on the document each time the state changes, and it bubbles.

When the state changes the browser queues a task that fires the event. Other specifications hook into the same step, and the HTML Standard names Web NFC and the Device Posture API as examples. When a document is being unloaded, the hidden steps run during the unload itself rather than in a later task, so visibilitychange is the last reliable signal a page gets before it goes away.

The sample uses a stand-in document built from EventTarget, so the logic runs in Node.

const doc = new EventTarget();
doc.visibilityState = "visible";
Object.defineProperty(doc, "hidden", { get() { return this.visibilityState === "hidden"; } });
const log = [];
doc.addEventListener("visibilitychange", () => {
  log.push(`${doc.visibilityState} (hidden=${doc.hidden}) -> ` + (doc.hidden ? "pause video, flush analytics" : "resume video"));
});
for (const s of ["hidden", "visible", "hidden"]) { doc.visibilityState = s; doc.dispatchEvent(new Event("visibilitychange")); }
console.log(log.join("\n"));

Output:

hidden (hidden=true) -> pause video, flush analytics
visible (hidden=false) -> resume video
hidden (hidden=true) -> pause video, flush analytics

What is the difference between visibilitychange and blur?

visibilitychange tracks whether the page can be seen, while blur tracks keyboard focus. A window placed beside another one loses focus and fires blur, but its visibilityState stays "visible" because some of the viewport is still showing. Use focus and blur for input-focus logic and visibility for pausing media or polling.

Which browsers support the Page Visibility API?

All current engines support it. MDN compatibility data lists document.visibilityState and document.hidden from Chrome 33, Firefox 18, Safari 7 and Edge 12, and the visibilitychange event from Chrome 62, Firefox 56, Safari 14.1 and Edge 18.

Common pitfalls

  • Treating hidden as closed: a hidden tab can become visible again, so pause work and keep state instead of tearing everything down.
  • Using blur as a visibility signal: the window can be visible and unfocused. Read visibilityState when you need to know whether the user can see the page.
  • Waiting for unload to send analytics: the HTML Standard runs the hidden steps during the unload itself, so a visibilitychange handler is the place to flush data.
  • Assuming the event carries the state: the event has no state field. Read document.visibilityState inside the handler.
  • Reading the state at startup only: the initial state can already be "hidden" when a tab loads in the background, so start media only when visibilityState is "visible".
  • Expecting other APIs to run while hidden: PaymentRequest show() rejects with AbortError when the document is not visible.

Related terms

  • DOM — the document object that owns the property and the event
  • Event loop — the browser queues a task to fire visibilitychange
  • Throttle — a way to limit work done while the page is visible
  • Intersection Observer — reports visibility of single elements instead of the whole page
  • Idle Detection API — reports user inactivity, which page visibility alone cannot show

See also

  • Term: Intersection Observer — the per-element counterpart to page-level visibility