DEV Community

Cover image for Your Web App Keeps Running in Hidden Tabs — Here’s the Fix
Alkesh Gupta
Alkesh Gupta

Posted on

Your Web App Keeps Running in Hidden Tabs — Here’s the Fix

Have you ever had music continue playing after switching tabs?
Or dashboards keep polling APIs while you’re not even watching?

Modern web apps often continue working unnecessarily when the tab is hidden — leading to wasted resources, wrong analytics, and a worse user experience.

This article introduces a lightweight utility that solves that problem cleanly and consistently:
@alkeshgupta/tab-visibility

Let's deep-dive into why tab visibility matters, how the browser behaves behind the scenes, and how you can leverage it in React or vanilla JavaScript immediately.

⭐ If you find this useful, consider leaving a star on GitHub!


🧠 The Hidden Problem You Don’t Notice (But Your Users Do)

When a browser tab goes into the background:

  • Animations keep running

  • Videos continue playing

  • Timers continue ticking

  • API calls keep firing

  • Game loops keep looping

This leads to:

  • 🔋 Battery drain

  • 🐌 Performance degradation

  • 📉 Incorrect business metrics

  • 📊 Wasted backend server resources

  • 😡 Annoying UX (media keeps playing when tabbed away)

Developers often forget to pause/resume behavior based on tab visibility.


🔍 The Browser’s Behavior: A Quick Primer

Browsers expose the Page Visibility API, which gives:

  • document.visibilityState

  • visibilitychange event

The state can be:

  • visible

  • hidden

When a user switches tabs, minimizes the browser, or switches focus — the event fires.

Manually handling this looks like:

document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden") {
    pauseThings();
  } else {
    resumeThings();
  }
});
Enter fullscreen mode Exit fullscreen mode

But as you scale your application:

  • You need multiple listeners
  • You need to clean them up
  • You need to debounce/throttle events
  • You need media control logic
  • You need consistent behavior across browsers

It gets messy!


❌ Traditional Implementation Pitfalls

Most developers:

  • Forget to remove listeners
  • Duplicate logic across components
  • Handle blur/focus inconsistently
  • Mix UI and lifecycle logic incorrectly
  • Don’t centralize presence handling

Even in React, it’s easy to introduce memory leaks.
So we abstracted it.


✅ What @alkeshgupta/tab-visibility Solves

This utility:
✔️ Detects tab visibility changes
✔️ Provides clean callbacks
✔️ Supports multiple handlers
✔️ Automatically pauses <video> & <audio> elements
✔️ Works in React AND plain JavaScript
✔️ Requires zero configuration

One API to rule them all.
No boilerplate.


✨ Key Features

  • onShow(callback)
  • onHide(callback)
  • autoPauseMedia()

  • Multi-callback support

  • Works globally

  • Simple and intuitive


📦 Installation

npm install @alkeshgupta/tab-visibility
Enter fullscreen mode Exit fullscreen mode

or

yarn add @alkeshgupta/tab-visibility
Enter fullscreen mode Exit fullscreen mode

🧠 Basic Usage

import { useTabVisibility } from "@alkeshgupta/tab-visibility";

useTabVisibility.onShow(() => {
  console.log("Tab is visible again!");
});

useTabVisibility.onHide(() => {
  console.log("User switched tab!");
});
Enter fullscreen mode Exit fullscreen mode

⚛️ React Usage Example

Works anywhere inside your components.

import { useEffect } from "react";
import { useTabVisibilityHook } from "@alkeshgupta/tab-visibility";

function MyComponent() {
  useTabVisibilityHook({
    onHide: () => {
      console.log("User switched tab!");
    },

    onShow: () => {
      console.log("Tab is visible again!");
    },
  });

  return <div>Watching tab visibility</div>;
}

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

You can register as many event handlers as you want.


🔊 Auto Pause Media (Video/Audio)

Automatically pause all <video> and <audio> elements when the tab becomes hidden:

import { autoPauseMedia } from "@alkeshgupta/tab-visibility";

useEffect(() => {
  autoPauseMedia();
}, []);
Enter fullscreen mode Exit fullscreen mode

Optional - Ignore Muted Media:

import { autoPauseMedia } from "@alkeshgupta/tab-visibility";

useEffect(() => {
  autoPauseMedia({ ignoreMuted: true });
}, []);
Enter fullscreen mode Exit fullscreen mode

🧩 Vanilla Example (HTML)

Works anywhere inside your components.

<script type="module">
  import { useTabVisibility } from "https://cdn.jsdelivr.net/npm/@alkeshgupta/tab-visibility@1.0.1/src/index.js";

  useTabVisibility.onShow(() => {
    console.log("Tab is visible again!");
  });

  useTabVisibility.onHide(() => {
    console.log("User switched tab!");
  });
</script>
Enter fullscreen mode Exit fullscreen mode

🧬 API Reference

onShow(callback: Function)

Registers a function to run when tab becomes visible.

onShow(() => console.log("Welcome back!"));
Enter fullscreen mode Exit fullscreen mode

onHide(callback: Function)

Registers a function to run when tab becomes hidden.

onHide(() => console.log("Hey come back 👀"));
Enter fullscreen mode Exit fullscreen mode

autoPauseMedia()

Pauses all video and audio elements on the page.

autoPauseMedia();
Enter fullscreen mode Exit fullscreen mode

autoPauseMedia({ ignoreMuted: true })

Pauses all unmuted video and audio elements on the page.

autoPauseMedia({ ignoreMuted: true });
Enter fullscreen mode Exit fullscreen mode

🧪 Testing Tip

To test locally:

  • Open devtools
  • Start logging visibility events
  • Switch tabs
  • Observe:
    • event triggers
    • media pausing
    • batched handlers

No special instrumentation required.


🔍 When Should You Use This?

Ideal for:

  • Media streaming apps
  • Dashboards
  • Trading charts
  • Data visualizations
  • Pomodoro timers
  • Meeting & call apps
  • Games
  • Chat presence indicators

⚙️ Inside the Library (Technical Architecture)

Internally, the utility:

  • Registers a single visibilitychange listener
  • Stores onShow/onHide callbacks in arrays
  • Executes them sequentially
  • Optionally inspects DOM media nodes
  • Avoids duplicate attachment
  • Works globally across modules

This prevents event chaos.


🛑 Common Pitfalls (Solved)

Without this:

  • You may pause media incorrectly
  • Forget cleanup
  • Attach listeners multiple times
  • Cause memory leaks
  • Miss subtle visibility states

This utility removes risk.


📉 Browser Throttling Behavior

Background tabs aggressively throttle:

  • setInterval
  • setTimeout
  • JavaScript execution priority
  • requestAnimationFrame

So if you rely on timers — visibility is essential.


📜 Browser Support

✅ Chrome\
✅ Firefox\
✅ Edge\
✅ Safari\
✅ Opera

Uses the standard visibilitychange API, widely supported.


📏 Notes

  • All callbacks can be registered multiple times.
  • Order of callbacks is preserved.
  • Works in frameworks (React, Next.js, etc.) after mount.

📎 Links

GitHub Repo:
https://github.com/thealkeshgupta/tab-visibility

npm Package:
https://www.npmjs.com/package/@alkeshgupta/tab-visibility

If this saved your time — please ⭐ on GitHub!


🤝 Contributing

PRs are welcome!
Add features, fix edge cases, or open an issue.


📜 License

MIT — free for commercial & personal use.


✅ Conclusion

Handling tab visibility properly:

  • Saves CPU
  • Reduces power consumption
  • Improves UX
  • Optimizes analytics
  • Keeps media behavior sane

And instead of manually wiring event listeners + cleanup logic in every feature, you now have a reusable utility.

Try @alkeshgupta/tab-visibility in your next project 🚀


Top comments (0)