webdevpuneet.com

Page Visibility API Indicator — Free visibilitychange Demo

Page Visibility API Indicator · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

<section class="pvi-wrap">
  <span class="pvi-tag">page visibility api · document.visibilitystate</span>
  <h1>Tab visibility</h1>
  <p>Switch to another tab, minimize the window, or come back — this reads the real <code>document.visibilityState</code>.</p>

  <div class="pvi-card">
    <div class="pvi-dot" id="pviDot"></div>
    <div>
      <div class="pvi-state" id="pviState">visible</div>
      <div class="pvi-sub" id="pviSub">This tab is currently in the foreground.</div>
    </div>
  </div>

  <div class="pvi-stats">
    <div class="pvi-stat"><span id="pviVisibleTime">0s</span><small>time visible</small></div>
    <div class="pvi-stat"><span id="pviHiddenTime">0s</span><small>time hidden</small></div>
    <div class="pvi-stat"><span id="pviSwitches">0</span><small>state changes</small></div>
  </div>

  <div class="pvi-log-head">Change log</div>
  <ul class="pvi-log" id="pviLog"></ul>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Real visibilityState reads
Not a guess — the browser's own value.
visibilitychange listener
Fires on every genuine foreground/background switch.
Accurate time accounting
Date.now() deltas, not polling estimates.
Capped live change log
Timestamped entries, trimmed at 25 rows.
Switch counter
Tallies total visibility transitions.
Unsupported-browser fallback
Static labeled message if the API is missing.
No blur/focus false positives
Avoids the common in-page-focus pitfall.
Zero dependencies
Plain DOM APIs only.

About this UI Snippet

Page Visibility API Indicator — Real Tab Visibility, Logged Live

Screenshot of the Page Visibility API Indicator snippet rendered live

This snippet reads the browser's actual Page Visibility API — document.visibilityState and the visibilitychange event — to show, in real time, whether the current tab is in the foreground or has been switched away from, minimized, or covered. It's the exact mechanism behind why a video pauses when you switch tabs or a dashboard stops polling in the background.

Why not blur/focus?

It's tempting to reach for window blur/focus events instead, but those fire for reasons that have nothing to do with tab visibility — clicking into a browser extension popup, opening dev tools, or focus moving to another window on some platforms can all fire blur while the tab is still fully visible on screen. document.visibilityState is more precise: it reports 'visible', 'hidden', and (on some platforms) 'prerender', driven by whether the tab's content is actually being rendered to the user, and visibilitychange fires exactly when that changes — including tab switches, window minimizing, and screen locking.

What real apps do with this

Video players pause playback and mute audio processing when hidden; dashboards and live feeds stop polling APIs to save bandwidth and battery; games pause their update loop; analytics tools log accurate "time on page" instead of counting background time. This snippet demonstrates the pattern directly: it accumulates separate visible-time and hidden-time counters using Date.now() deltas measured between visibilitychange events, so the numbers are a real elapsed-time record, not an estimate.

A live, capped change log

Every transition appends a timestamped entry to a log capped at 25 rows (oldest entries are trimmed), so you can watch a session's visibility history accumulate — useful for demonstrating to teammates exactly when and how often a page loses focus during a real usage session.

Handling unsupported browsers

The Page Visibility API has been standard for well over a decade, so unsupported browsers are effectively nonexistent today — but the snippet still checks typeof document.hidden !== 'undefined' before wiring the listener, and falls back to a clearly labeled static "always visible" message rather than silently doing nothing if the check ever fails. Pair this with a screen wake lock toggle to build a full "pause when backgrounded" media control, or a network information badge for a broader environment-awareness dashboard.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSThe indicator renders showing "visible".
  2. 2
    Switch to another tabThe dot turns red and state flips to "hidden".
  3. 3
    Come backState flips back; the change log records both events.
  4. 4
    Watch the countersVisible-time and hidden-time accumulate from real deltas.
  5. 5
    Minimize the windowSame hidden state, confirming it's not just a tab check.
  6. 6
    Wire it to real workGate polling/video/animation on document.visibilityState.

Real-world uses

Common Use Cases

Video and audio players
Pause playback when the tab is hidden, using the browser's real visibilitychange event and document.visibilityState instead of guessing.
Live dashboards
Pause polling while nobody is looking, and pair with an uptime status page that resumes the moment the tab returns.
Accurate time-on-page analytics
Track true engaged time on article and docs pages by excluding background tabs, using simple Date.now() deltas between visibility changes rather than rough polling estimates.
Games and animations
Pause the update loop when the tab is backgrounded to save battery and avoid a huge time step when the player comes back.
Presence indicators
Combine with a live visitor counter so presence counts reflect who is actually viewing, not who merely has a tab open.
Related: Unit Converter
See the Unit Converter for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

document.visibilityState reports "hidden" when the tab is switched away from, the browser window is minimized, or on some platforms when the screen is locked — essentially any time the page's content is not being rendered to the user. It reports "visible" only when the tab is actually on screen and in the foreground.

blur and focus fire for reasons unrelated to tab visibility, like clicking into a browser extension popup or opening developer tools, which can trigger a false "hidden" reading while the tab is still fully visible. document.visibilityState and visibilitychange are purpose-built for this exact question and don't have those false positives.

Each time visibilitychange fires, the code takes Date.now() and computes the elapsed milliseconds since the last change, adding that delta to whichever counter (visible or hidden) matches the state that just ended. This is a real accumulated elapsed-time measurement, not a polling-based estimate, so it stays accurate regardless of how long each state lasts.

Common patterns: pause video/audio playback and disconnect visualizer loops, stop or slow down polling intervals for live data, pause requestAnimationFrame-driven animations or games, and skip sending analytics "heartbeat" pings. Resume all of it in the visible branch of the same visibilitychange handler.

Register the visibilitychange listener in a mount effect and remove it on unmount. Store visibilityState, the time counters, and the log entries in component state (or a ref for the raw timers to avoid re-render overhead), updating state from inside the handler exactly as the vanilla version updates the DOM directly.