DetectTab 🔍
A comprehensive tab detection and management library for web applications with TypeScript support.

🚀 Features
- Tab Visibility Detection: Detect when your tab becomes visible or hidden
- Focus Management: Track when your tab gains or loses focus
- Time Tracking: Monitor time spent visible, hidden, and in current state
- Event System: Comprehensive event listeners for all tab state changes
- Performance Optimization: Built-in debouncing and efficient event handling
- Persistence: Optional localStorage integration for session tracking
- TypeScript Support: Full TypeScript definitions included
- Browser Compatibility: Works across modern browsers with graceful degradation
- Lightweight: Minimal bundle size with zero dependencies
- Easy Integration: Simple API with both instance-based and functional approaches
📦 Installation
npm install detect-tab
yarn add detect-tab
pnpm add detect-tab
🏃♂️ Quick Start
This guide provides detailed instructions on how to use the DetectTab library in various scenarios.
Installation & Setup
1. Install the Package
npm install detect-tab
2. Import in Your Project
ES6 Modules (Recommended)
import { DetectTab, TabState, TabEvent } from 'detect-tab';
CommonJS
const { DetectTab, TabState, TabEvent } = require('detect-tab');
Browser (UMD)
<script src="node_modules/detect-tab/dist/index.umd.js"></script>
<script>
const { DetectTab, TabState, TabEvent } = window.DetectTab;
</script>
Basic Usage
Quick Start - Default Instance
For simple usage, you can use the pre-created default instance:
import { isVisible, isFocused, getState, getTabInfo } from 'detect-tab';
console.log('Is visible:', isVisible());
console.log('Is focused:', isFocused());
console.log('Current state:', getState());
const info = getTabInfo();
console.log('Tab info:', info);
Creating Custom Instances
For more control, create your own instance:
import { DetectTab } from 'detect-tab';
const tabDetector = new DetectTab({
autoStart: true,
debounceTime: 100,
debug: false,
persistent: false,
storageKey: 'myTab'
});
Event Handling
State Change Events
Listen for when the tab becomes visible or hidden:
tabDetector.onStateChange((state, info) => {
switch (state) {
case TabState.VISIBLE:
console.log('Tab is now visible');
resumeAnimations();
break;
case TabState.HIDDEN:
console.log('Tab is now hidden');
pauseAnimations();
break;
case TabState.PRERENDER:
console.log('Tab is being prerendered');
break;
}
});
Focus Change Events
Listen for when the tab gains or loses focus:
tabDetector.onFocusChange((focused, info) => {
if (focused) {
console.log('Tab gained focus');
clearNotificationBadge();
resumeKeyboardShortcuts();
} else {
console.log('Tab lost focus');
pauseKeyboardShortcuts();
}
});
Specific Event Listeners
Listen for specific browser events:
tabDetector.on(TabEvent.VISIBILITY_CHANGE, (info) => {
console.log('Visibility changed:', info.visible);
});
tabDetector.on(TabEvent.FOCUS, (info) => {
console.log('Tab focused');
});
tabDetector.on(TabEvent.BLUR, (info) => {
console.log('Tab blurred');
});
tabDetector.on(TabEvent.BEFORE_UNLOAD, (info) => {
console.log('Page is about to unload');
saveUserData(info);
});
tabDetector.on(TabEvent.PAGE_HIDE, (info) => {
console.log('Page is hidden (mobile switch, etc.)');
});
Common Use Cases
1. Performance Optimization
Pause expensive operations when the tab is hidden:
import { DetectTab, TabState } from 'detect-tab';
const tabDetector = new DetectTab();
let animationFrame;
let updateInterval;
tabDetector.onStateChange((state) => {
if (state === TabState.HIDDEN) {
if (animationFrame) {
cancelAnimationFrame(animationFrame);
}
clearInterval(updateInterval);
updateInterval = setInterval(updateData, 10000);
document.querySelectorAll('video').forEach(video => {
video.pause();
});
} else if (state === TabState.VISIBLE) {
startAnimations();
clearInterval(updateInterval);
updateInterval = setInterval(updateData, 1000);
document.querySelectorAll('video').forEach(video => {
video.play();
});
}
});
2. User Experience Enhancement
Improve UX with smart notifications and state management:
import { DetectTab, TabEvent } from 'detect-tab';
const tabDetector = new DetectTab();
let notificationCount = 0;
tabDetector.onFocusChange((focused) => {
if (focused) {
document.title = document.title.replace(/^\(\d+\) /, '');
notificationCount = 0;
markMessagesAsRead();
}
});
function onNewMessage(message) {
if (!tabDetector.isVisible()) {
notificationCount++;
document.title = `(${notificationCount}) ${originalTitle}`;
if (Notification.permission === 'granted') {
new Notification('New message', {
body: message.text,
icon: '/favicon.ico'
});
}
}
}
3. Analytics and Tracking
Track user engagement and session data:
import { DetectTab, TabEvent } from 'detect-tab';
const tabDetector = new DetectTab({
persistent: true,
debug: false
});
tabDetector.on(TabEvent.FOCUS, (info) => {
analytics.track('session_start', {
timestamp: Date.now(),
totalVisibleTime: info.totalVisibleTime
});
});
tabDetector.on(TabEvent.VISIBILITY_CHANGE, (info) => {
analytics.track('engagement_change', {
visible: info.visible,
timeInState: info.timeInState,
visibilityChanges: info.visibilityChanges
});
});
tabDetector.on(TabEvent.BEFORE_UNLOAD, (info) => {
const stats = tabDetector.getTimeStats();
analytics.track('session_end', {
totalVisibleTime: info.totalVisibleTime,
totalHiddenTime: info.totalHiddenTime,
visibilityChanges: info.visibilityChanges,
sessionDuration: info.totalVisibleTime + info.totalHiddenTime
});
});
4. Real-time Features Management
Optimize real-time connections based on tab state:
import { DetectTab } from 'detect-tab';
const tabDetector = new DetectTab();
let websocket;
let updateFrequency = 1000;
function adjustWebSocketFrequency() {
if (websocket) {
websocket.send(JSON.stringify({
type: 'set_frequency',
frequency: updateFrequency
}));
}
}
tabDetector.onStateChange((state, info) => {
if (state === 'hidden') {
updateFrequency = 30000;
} else if (state === 'visible') {
updateFrequency = info.focused ? 1000 : 5000;
}
adjustWebSocketFrequency();
});
tabDetector.onFocusChange((focused) => {
if (tabDetector.isVisible()) {
updateFrequency = focused ? 1000 : 5000;
adjustWebSocketFrequency();
}
});
5. Battery and Performance Optimization
Advanced optimizations for mobile and battery life:
import { DetectTab, TabState } from 'detect-tab';
const tabDetector = new DetectTab();
class PerformanceManager {
constructor() {
this.setupTabDetection();
this.highPerformanceMode = true;
}
setupTabDetection() {
tabDetector.onStateChange((state) => {
switch (state) {
case TabState.HIDDEN:
this.enableBatterySavingMode();
break;
case TabState.VISIBLE:
this.disableBatterySavingMode();
break;
}
});
}
enableBatterySavingMode() {
this.highPerformanceMode = false;
this.setAnimationFrameRate(15);
this.pauseBackgroundTasks();
this.setNetworkRequestInterval(60000);
console.log('Battery saving mode enabled');
}
disableBatterySavingMode() {
this.highPerformanceMode = true;
this.setAnimationFrameRate(60);
this.resumeBackgroundTasks();
this.setNetworkRequestInterval(5000);
console.log('High performance mode enabled');
}
setAnimationFrameRate(fps) {
this.targetFrameTime = 1000 / fps;
}
pauseBackgroundTasks() {
}
resumeBackgroundTasks() {
}
setNetworkRequestInterval(interval) {
clearInterval(this.networkInterval);
this.networkInterval = setInterval(() => {
if (tabDetector.isVisible()) {
this.fetchUpdates();
}
}, interval);
}
fetchUpdates() {
}
}
const perfManager = new PerformanceManager();
Time Statistics
Getting Time Information
const stats = tabDetector.getTimeStats();
console.log('Visible time:', stats.totalVisibleTime);
console.log('Hidden time:', stats.totalHiddenTime);
console.log('Current state time:', stats.timeInCurrentState);
const info = tabDetector.getTabInfo();
console.log('Raw visible time (ms):', info.totalVisibleTime);
console.log('Raw hidden time (ms):', info.totalHiddenTime);
console.log('Visibility changes:', info.visibilityChanges);
Custom Time Tracking
class TimeTracker {
constructor() {
this.tabDetector = new DetectTab({ persistent: true });
this.setupTracking();
}
setupTracking() {
setInterval(() => {
this.updateTimeDisplay();
}, 1000);
this.tabDetector.onStateChange((state, info) => {
const totalTime = info.totalVisibleTime + info.totalHiddenTime;
if (totalTime > 0 && totalTime % 300000 === 0) {
console.log(`Milestone: ${Math.floor(totalTime / 60000)} minutes total time`);
}
});
}
updateTimeDisplay() {
const stats = this.tabDetector.getTimeStats();
const displayEl = document.getElementById('time-display');
if (displayEl) {
displayEl.innerHTML = `
<div>Visible: ${stats.totalVisibleTime}</div>
<div>Hidden: ${stats.totalHiddenTime}</div>
<div>Current: ${stats.timeInCurrentState}</div>
`;
}
}
}
Persistence and Storage
Enable Persistence
const tabDetector = new DetectTab({
persistent: true,
storageKey: 'myApp_tabData'
});
Manual Data Management
tabDetector.clearPersistedData();
tabDetector.reset();
const info = tabDetector.getTabInfo();
localStorage.setItem('customTabData', JSON.stringify(info));
Cleanup and Memory Management
Proper Cleanup
function cleanup() {
tabDetector.destroy();
}
window.addEventListener('beforeunload', cleanup);
useEffect(() => {
return () => {
tabDetector.destroy();
};
}, []);
Event Listener Management
const stateCallback = (state, info) => { };
const focusCallback = (focused, info) => { };
tabDetector.onStateChange(stateCallback);
tabDetector.onFocusChange(focusCallback);
tabDetector.offStateChange(stateCallback);
tabDetector.offFocusChange(focusCallback);
tabDetector.destroy();
Error Handling
Graceful Error Handling
try {
const tabDetector = new DetectTab({
debug: true
});
tabDetector.onStateChange((state, info) => {
try {
performStateChange(state);
} catch (error) {
console.error('Error in state change handler:', error);
}
});
} catch (error) {
console.error('Failed to initialize DetectTab:', error);
document.addEventListener('visibilitychange', () => {
console.log('Visibility changed (fallback)');
});
}
Browser Compatibility
Feature Detection
import { isVisibilityAPISupported, isBrowserSupported } from 'detect-tab';
if (!isBrowserSupported()) {
console.warn('DetectTab requires a browser environment');
}
if (!isVisibilityAPISupported()) {
console.warn('Page Visibility API not supported, using focus/blur only');
}
TypeScript Usage
Type-Safe Implementation
import {
DetectTab,
TabState,
TabEvent,
TabInfo,
DetectTabOptions,
TabStateChangeCallback
} from 'detect-tab';
class TypedTabManager {
private tabDetector: DetectTab;
private callbacks: Map<string, Function> = new Map();
constructor(options?: DetectTabOptions) {
this.tabDetector = new DetectTab(options);
this.setupEventHandlers();
}
private setupEventHandlers(): void {
const stateCallback: TabStateChangeCallback = (state: TabState, info: TabInfo) => {
this.handleStateChange(state, info);
};
this.tabDetector.onStateChange(stateCallback);
this.callbacks.set('stateChange', stateCallback);
}
private handleStateChange(state: TabState, info: TabInfo): void {
switch (state) {
case TabState.VISIBLE:
this.onVisible(info);
break;
case TabState.HIDDEN:
this.onHidden(info);
break;
default:
console.warn(`Unhandled state: ${state}`);
}
}
private onVisible(info: TabInfo): void {
console.log('Tab visible:', info);
}
private onHidden(info: TabInfo): void {
console.log('Tab hidden:', info);
}
public getInfo(): TabInfo {
return this.tabDetector.getTabInfo();
}
public destroy(): void {
this.tabDetector.destroy();
this.callbacks.clear();
}
}
Graceful Degradation
- If Page Visibility API is not supported, the library falls back to basic focus/blur detection
- If localStorage is not available, persistence features are disabled
- All features degrade gracefully without breaking functionality
Supported Browsers
- ✅ Chrome 14+
- ✅ Firefox 18+
- ✅ Safari 7+
- ✅ Edge 12+
- ✅ Opera 15+
- ✅ iOS Safari 7+
- ✅ Android Browser 4.4+
🏗️ Development
Setup
git clone https://github.com/yourusername/detect-tab.git
cd detect-tab
npm install
Build
npm run build
npm run build:types
npm run dev
Testing
npm test
npm run test:watch
Linting
npm run lint
npm run lint:fix
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Development Guidelines
- Code Style: Follow the existing TypeScript/ESLint configuration
- Testing: Add tests for new features
- Documentation: Update README and JSDoc comments
- Backwards Compatibility: Maintain API compatibility
Commit Message Format
type(scope): description
[optional body]
[optional footer]
Types: feat, fix, docs, style, refactor, test, chore
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙋♂️ Support
🎯 Roadmap
📊 Changelog
v1.0.0
- Initial release
- Full TypeScript support
- Comprehensive tab detection
- Event system
- Persistence support
- Browser compatibility
- Complete test coverage
Made with ❤️ by [meteor314]