detect-tab
Version:
A comprehensive tab detection and management library for web applications
771 lines (588 loc) • 19.8 kB
Markdown
A comprehensive tab detection and management library for web applications with TypeScript support.
[](https://badge.fury.io/js/detect-tab)
[](https://opensource.org/licenses/MIT)
[](http://www.typescriptlang.org/)
- **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
```bash
npm install detect-tab
```
```bash
yarn add detect-tab
```
```bash
pnpm add detect-tab
```
This guide provides detailed instructions on how to use the DetectTab library in various scenarios.
```bash
npm install detect-tab
```
```javascript
import { DetectTab, TabState, TabEvent } from 'detect-tab';
```
```javascript
const { DetectTab, TabState, TabEvent } = require('detect-tab');
```
```html
<script src="node_modules/detect-tab/dist/index.umd.js"></script>
<script>
const { DetectTab, TabState, TabEvent } = window.DetectTab;
</script>
```
For simple usage, you can use the pre-created default instance:
```javascript
import { isVisible, isFocused, getState, getTabInfo } from 'detect-tab';
// Check current state
console.log('Is visible:', isVisible());
console.log('Is focused:', isFocused());
console.log('Current state:', getState());
// Get detailed information
const info = getTabInfo();
console.log('Tab info:', info);
```
For more control, create your own instance:
```javascript
import { DetectTab } from 'detect-tab';
const tabDetector = new DetectTab({
autoStart: true, // Start detection immediately
debounceTime: 100, // Debounce events by 100ms
debug: false, // Disable debug logging
persistent: false, // Don't persist data
storageKey: 'myTab' // Custom storage key
});
```
Listen for when the tab becomes visible or hidden:
```javascript
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;
}
});
```
Listen for when the tab gains or loses focus:
```javascript
tabDetector.onFocusChange((focused, info) => {
if (focused) {
console.log('Tab gained focus');
clearNotificationBadge();
resumeKeyboardShortcuts();
} else {
console.log('Tab lost focus');
pauseKeyboardShortcuts();
}
});
```
Listen for specific browser events:
```javascript
// Visibility change
tabDetector.on(TabEvent.VISIBILITY_CHANGE, (info) => {
console.log('Visibility changed:', info.visible);
});
// Focus events
tabDetector.on(TabEvent.FOCUS, (info) => {
console.log('Tab focused');
});
tabDetector.on(TabEvent.BLUR, (info) => {
console.log('Tab blurred');
});
// Page lifecycle events
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.)');
});
```
Pause expensive operations when the tab is hidden:
```javascript
import { DetectTab, TabState } from 'detect-tab';
const tabDetector = new DetectTab();
let animationFrame;
let updateInterval;
tabDetector.onStateChange((state) => {
if (state === TabState.HIDDEN) {
// Pause animations
if (animationFrame) {
cancelAnimationFrame(animationFrame);
}
// Reduce update frequency
clearInterval(updateInterval);
updateInterval = setInterval(updateData, 10000); // 10 seconds
// Pause videos
document.querySelectorAll('video').forEach(video => {
video.pause();
});
} else if (state === TabState.VISIBLE) {
// Resume animations
startAnimations();
// Normal update frequency
clearInterval(updateInterval);
updateInterval = setInterval(updateData, 1000); // 1 second
// Resume videos
document.querySelectorAll('video').forEach(video => {
video.play();
});
}
});
```
Improve UX with smart notifications and state management:
```javascript
import { DetectTab, TabEvent } from 'detect-tab';
const tabDetector = new DetectTab();
let notificationCount = 0;
// Clear notifications when user returns
tabDetector.onFocusChange((focused) => {
if (focused) {
// Clear notification badge
document.title = document.title.replace(/^\(\d+\) /, '');
notificationCount = 0;
// Mark messages as read
markMessagesAsRead();
}
});
// Show notification when hidden and new message arrives
function onNewMessage(message) {
if (!tabDetector.isVisible()) {
notificationCount++;
document.title = `(${notificationCount}) ${originalTitle}`;
// Show browser notification
if (Notification.permission === 'granted') {
new Notification('New message', {
body: message.text,
icon: '/favicon.ico'
});
}
}
}
```
Track user engagement and session data:
```javascript
import { DetectTab, TabEvent } from 'detect-tab';
const tabDetector = new DetectTab({
persistent: true, // Save data across sessions
debug: false
});
// Track session start
tabDetector.on(TabEvent.FOCUS, (info) => {
analytics.track('session_start', {
timestamp: Date.now(),
totalVisibleTime: info.totalVisibleTime
});
});
// Track engagement
tabDetector.on(TabEvent.VISIBILITY_CHANGE, (info) => {
analytics.track('engagement_change', {
visible: info.visible,
timeInState: info.timeInState,
visibilityChanges: info.visibilityChanges
});
});
// Track session end
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
});
});
```
Optimize real-time connections based on tab state:
```javascript
import { DetectTab } from 'detect-tab';
const tabDetector = new DetectTab();
let websocket;
let updateFrequency = 1000; // 1 second
function adjustWebSocketFrequency() {
if (websocket) {
websocket.send(JSON.stringify({
type: 'set_frequency',
frequency: updateFrequency
}));
}
}
tabDetector.onStateChange((state, info) => {
if (state === 'hidden') {
// Reduce frequency when hidden
updateFrequency = 30000; // 30 seconds
} else if (state === 'visible') {
// Normal frequency when visible
updateFrequency = info.focused ? 1000 : 5000; // 1s focused, 5s unfocused
}
adjustWebSocketFrequency();
});
tabDetector.onFocusChange((focused) => {
if (tabDetector.isVisible()) {
updateFrequency = focused ? 1000 : 5000;
adjustWebSocketFrequency();
}
});
```
Advanced optimizations for mobile and battery life:
```javascript
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;
// Reduce animation frame rate
this.setAnimationFrameRate(15); // 15 FPS
// Pause non-essential background tasks
this.pauseBackgroundTasks();
// Reduce network requests
this.setNetworkRequestInterval(60000); // 1 minute
console.log('Battery saving mode enabled');
}
disableBatterySavingMode() {
this.highPerformanceMode = true;
// Normal animation frame rate
this.setAnimationFrameRate(60); // 60 FPS
// Resume background tasks
this.resumeBackgroundTasks();
// Normal network requests
this.setNetworkRequestInterval(5000); // 5 seconds
console.log('High performance mode enabled');
}
setAnimationFrameRate(fps) {
this.targetFrameTime = 1000 / fps;
// Implement frame rate limiting logic
}
pauseBackgroundTasks() {
// Pause data processing, analytics, etc.
}
resumeBackgroundTasks() {
// Resume background tasks
}
setNetworkRequestInterval(interval) {
clearInterval(this.networkInterval);
this.networkInterval = setInterval(() => {
if (tabDetector.isVisible()) {
this.fetchUpdates();
}
}, interval);
}
fetchUpdates() {
// Fetch data updates
}
}
const perfManager = new PerformanceManager();
```
```javascript
// Get formatted time statistics
const stats = tabDetector.getTimeStats();
console.log('Visible time:', stats.totalVisibleTime);
console.log('Hidden time:', stats.totalHiddenTime);
console.log('Current state time:', stats.timeInCurrentState);
// Get raw time data
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);
```
```javascript
class TimeTracker {
constructor() {
this.tabDetector = new DetectTab({ persistent: true });
this.setupTracking();
}
setupTracking() {
// Update display every second
setInterval(() => {
this.updateTimeDisplay();
}, 1000);
// Log milestone times
this.tabDetector.onStateChange((state, info) => {
const totalTime = info.totalVisibleTime + info.totalHiddenTime;
// Log every 5 minutes of total time
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>
`;
}
}
}
```
```javascript
const tabDetector = new DetectTab({
persistent: true,
storageKey: 'myApp_tabData'
});
// Data is automatically saved to localStorage
// and restored when the page loads
```
```javascript
// Clear stored data
tabDetector.clearPersistedData();
// Reset all statistics
tabDetector.reset();
// Get current data for custom storage
const info = tabDetector.getTabInfo();
localStorage.setItem('customTabData', JSON.stringify(info));
```
```javascript
// When your app/component is destroyed
function cleanup() {
tabDetector.destroy();
}
// For single-page applications
window.addEventListener('beforeunload', cleanup);
// For React components
useEffect(() => {
return () => {
tabDetector.destroy();
};
}, []);
```
```javascript
// Add listeners
const stateCallback = (state, info) => { /* ... */ };
const focusCallback = (focused, info) => { /* ... */ };
tabDetector.onStateChange(stateCallback);
tabDetector.onFocusChange(focusCallback);
// Remove specific listeners
tabDetector.offStateChange(stateCallback);
tabDetector.offFocusChange(focusCallback);
// Or destroy the instance to remove all listeners
tabDetector.destroy();
```
```javascript
try {
const tabDetector = new DetectTab({
debug: true // Enable debug mode to see errors
});
// Add error handling for callbacks
tabDetector.onStateChange((state, info) => {
try {
// Your code here
performStateChange(state);
} catch (error) {
console.error('Error in state change handler:', error);
// Fallback behavior
}
});
} catch (error) {
console.error('Failed to initialize DetectTab:', error);
// Fallback: use basic visibility detection
document.addEventListener('visibilitychange', () => {
console.log('Visibility changed (fallback)');
});
}
```
```javascript
import { isVisibilityAPISupported, isBrowserSupported } from 'detect-tab';
if (!isBrowserSupported()) {
console.warn('DetectTab requires a browser environment');
// Fallback logic
}
if (!isVisibilityAPISupported()) {
console.warn('Page Visibility API not supported, using focus/blur only');
// Limited functionality warning
}
```
```typescript
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();
}
}
```
- 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
- ✅ Chrome 14+
- ✅ Firefox 18+
- ✅ Safari 7+
- ✅ Edge 12+
- ✅ Opera 15+
- ✅ iOS Safari 7+
- ✅ Android Browser 4.4+
```bash
git clone https://github.com/yourusername/detect-tab.git
cd detect-tab
npm install
```
```bash
npm run build
npm run build:types
npm run dev
```
```bash
npm test
npm run test:watch
```
```bash
npm run lint
npm run lint:fix
```
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.
1. **Code Style**: Follow the existing TypeScript/ESLint configuration
2. **Testing**: Add tests for new features
3. **Documentation**: Update README and JSDoc comments
4. **Backwards Compatibility**: Maintain API compatibility
```
type(scope): description
[]
[]
```
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
- 📖 [Documentation](https://github.com/meteor314/detect-tab#readme)
- 🐛 [Issue Tracker](https://github.com/meteor314/detect-tab/issues)
- 💬 [Discussions](https://github.com/meteor314/detect-tab/discussions)
- [ ] React/Vue/Angular integration helpers
- [ ] Web Workers support
- [ ] Service Worker integration
- [ ] Performance metrics dashboard
- [ ] Advanced analytics features
- [ ] Mobile app integration (React Native/Ionic)
## 📊 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]