What is the visibilitychange Event?
The visibilitychange event is a DOM event that fires at the document when the contents of its tab have become visible or have been hidden. This event is part of the Page Visibility API, a web standard that enables developers to detect when a webpage is visible to the user and respond accordingly.
The Page Visibility API performs a simple but important function--it lets your application know when a page is visible to the user. This basic piece of information enables the creation of web pages that behave differently when they are not being viewed.
Key characteristics:
- The event is fired at the document, not the window
- It indicates the tab's visibility state has changed
- Part of the W3C Page Visibility specification
- Supported across all modern browsers since 2015-2021
- Not cancelable
As part of modern web development best practices, properly handling visibility changes helps create applications that respect user attention and system resources.
1// Simple visibility change handler2document.addEventListener('visibilitychange', () => {3 if (document.hidden) {4 console.log('Page is hidden');5 // Perform hidden state actions6 } else {7 console.log('Page is visible');8 // Perform visible state actions9 }10});11 12// Using visibilityState property13function getVisibilityState() {14 switch (document.visibilityState) {15 case 'visible':16 return 'Page is fully visible';17 case 'hidden':18 return 'Page is hidden';19 default:20 return 'Unknown state';21 }22}Understanding Visibility States
The Page Visibility API provides two properties for checking the current visibility state:
document.hidden Property
The document.hidden property returns true if the page is in a state considered to be hidden to the user, and false otherwise. This is the simplest way to check if your page is currently visible.
When a page is considered hidden:
- The user has minimized the browser window
- The page is on a background tab
- The browser is partially obscured by another window
- The device's screen is turned off (mobile)
- The user has switched to a different application (mobile)
document.visibilityState Property
The document.visibilityState property provides more granular information:
| Value | Description |
|---|---|
| visible | The page content is at least partially visible (foreground tab of a non-minimized window) |
| hidden | The page's content is not visible to the user |
Understanding these states is essential for building performant JavaScript applications that respond intelligently to user behavior.
Practical Use Cases
The visibilitychange event enables a wide range of practical optimizations for web applications:
Pausing Media Playback
One of the most common uses is pausing media when the user leaves the tab:
let video = document.querySelector('video');
let wasPlaying = false;
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
wasPlaying = !video.paused;
video.pause();
} else if (wasPlaying) {
video.play();
}
});
Analytics and Session Tracking
The transition to "hidden" is reliably the last event before a user potentially leaves your page:
document.onvisibilitychange = () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/api/analytics/session-end', JSON.stringify({
sessionDuration: Date.now() - sessionStartTime,
pagePath: window.location.pathname
}));
}
};
Reducing Polling and API Calls
Optimize network traffic by adjusting polling frequency:
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// Reduce polling frequency when hidden
clearInterval(pollInterval);
pollInterval = setInterval(fetchCriticalUpdates, 60000);
} else {
clearInterval(pollInterval);
pollInterval = setInterval(fetchUpdates, 5000);
}
});
Animation Performance
Pause animations when they're not visible to save resources:
document.addEventListener('visibilitychange', () => {
const animatedElements = document.querySelectorAll('.animated');
animatedElements.forEach(el => {
el.style.animationPlayState = document.hidden ? 'paused' : 'running';
});
});
These patterns are essential for building performant web applications that conserve resources and respect user attention.
Performance Implications and Browser Behavior
Understanding how browsers handle background tabs helps set realistic expectations.
Browser Throttling Behavior
Modern browsers automatically throttle background tabs:
- requestAnimationFrame: Stopped in background tabs to improve performance and battery life
- setTimeout/setInterval: Throttled with increased minimum delays (up to 1000ms in many browsers)
- Budget-based throttling: Timer tasks only run when budget is non-negative; budget regenerates ~10ms/second
Processes Exempt from Throttling
Some operations continue running:
- Audio-playing tabs: Not throttled (hence the importance of manual pausing)
- WebSockets/WebRTC: Unthrottled to prevent connection timeouts
- IndexedDB operations: Left unthrottled to prevent timeouts
While automatic throttling helps, explicitly using visibilitychange provides more control over resource management. This approach is a key aspect of optimizing web performance for modern applications.
Best Practices for Modern Web Development
Recommended Patterns
- Always clean up resources when hidden: Pause timers, animations, and API polling
- Use sendBeacon for analytics: Reliable even when the page is unloading
- Preserve user state: Save unsaved input when the page becomes hidden
- Resume gracefully: Stagger resumption to prevent resource spikes
Common Mistakes to Avoid
- Don't rely solely on focus/blur: Window focus ≠ page visibility
- Don't assume immediate resumption: Browsers may delay full performance restoration
- Don't use for authentication: Not a reliable user presence indicator
- Don't over-optimize trivial operations: Consider the overhead vs. benefit
React/Next.js Integration
function usePageVisibility() {
const [isVisible, setIsVisible] = useState(!document.hidden);
useEffect(() => {
const handleVisibilityChange = () => {
setIsVisible(!document.hidden);
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
return isVisible;
}
This custom hook can be used in any React or Next.js application to add visibility awareness with proper cleanup.
Browser Compatibility
The Page Visibility API is a well-established web standard:
| Browser | Version | Status |
|---|---|---|
| Chrome | 33+ | Full support |
| Firefox | 18+ | Full support |
| Safari | 7+ | Full support |
| Edge | 12+ | Full support |
| Mobile | All modern | Supported |
The API is considered Baseline Widely Available--you can rely on it across all modern browsers without vendor prefix fallbacks for most use cases.
For cross-browser compatibility in legacy scenarios, you can detect prefixed versions, though this is rarely needed in modern web development.
Frequently Asked Questions
What is the difference between visibilitychange and beforeunload?
The visibilitychange event fires whenever the tab becomes visible or hidden, while beforeunload only fires when the page is about to be unloaded. visibilitychange is more reliable for session tracking and can be used for analytics without blocking page navigation.
Does visibilitychange work on mobile devices?
Yes, visibilitychange works on mobile browsers. It fires when the user switches to a different app, when the browser is backgrounded, or when the device's screen is turned off.
Should I still use visibilitychange if browsers throttle background tabs?
Yes. While browsers throttle many operations automatically, explicit visibilitychange handling gives you more control. You can make more aggressive optimizations, save state more reliably, and provide better user experiences.
How does visibilitychange affect SEO?
visibilitychange itself doesn't directly affect SEO. However, using it to improve page performance (faster loading, less resource consumption) can contribute to better Core Web Vitals scores, which are ranking factors.