The Screen Wake Lock API allows web pages to prevent the device screen from entering sleep mode. This article covers its usage and practical applications.
Basic Concepts
Detecting Support
// Check API support
if ('wakeLock' in navigator) {
console.log('Screen Wake Lock API is supported');
} else {
console.log('Screen Wake Lock API is not supported');
}
Requesting Wake Lock
// Request screen wake lock
async function requestWakeLock() {
try {
const wakeLock = await navigator.wakeLock.request('screen');
console.log('Wake lock activated');
return wakeLock;
} catch (error) {
console.error('Could not activate wake lock:', error.message);
return null;
}
}
// Usage example
let wakeLock = null;
document.getElementById('start-btn').addEventListener('click', async () => {
wakeLock = await requestWakeLock();
});
Releasing Wake Lock
// Release wake lock
async function releaseWakeLock(wakeLock) {
if (wakeLock) {
await wakeLock.release();
console.log('Wake lock released');
}
}
// Usage example
document.getElementById('stop-btn').addEventListener('click', async () => {
await releaseWakeLock(wakeLock);
wakeLock = null;
});
Listening for Release Events
async function requestWakeLock() {
try {
const wakeLock = await navigator.wakeLock.request('screen');
// Listen for release event
wakeLock.addEventListener('release', () => {
console.log('Wake lock was released');
// Possible reasons:
// 1. Called release()
// 2. Page became hidden
// 3. Low battery
});
console.log('Wake lock activated');
return wakeLock;
} catch (error) {
console.error('Could not activate wake lock:', error);
return null;
}
}
Wake Lock Manager
Wrapper Class
class WakeLockManager {
constructor() {
this.wakeLock = null;
this.isSupported = 'wakeLock' in navigator;
this.listeners = new Set();
// Auto-reacquire when page becomes visible
document.addEventListener('visibilitychange', () => {
this.handleVisibilityChange();
});
}
get isActive() {
return this.wakeLock !== null && !this.wakeLock.released;
}
async request() {
if (!this.isSupported) {
console.warn('Screen Wake Lock API not supported');
return false;
}
if (this.isActive) {
console.log('Wake lock already active');
return true;
}
try {
this.wakeLock = await navigator.wakeLock.request('screen');
this.wakeLock.addEventListener('release', () => {
this.notify('release');
});
this.notify('acquire');
console.log('Wake lock activated');
return true;
} catch (error) {
console.error('Failed to request wake lock:', error);
this.notify('error', error);
return false;
}
}
async release() {
if (!this.isActive) return;
try {
await this.wakeLock.release();
this.wakeLock = null;
console.log('Wake lock released');
} catch (error) {
console.error('Failed to release wake lock:', error);
}
}
async handleVisibilityChange() {
if (document.visibilityState === 'visible' && this.wakeLock?.released) {
// Re-request wake lock when page becomes visible
console.log('Page visible, re-requesting wake lock');
await this.request();
}
}
onChange(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
notify(event, data = null) {
this.listeners.forEach(callback => callback(event, data));
}
}
// Usage
const wakeLockManager = new WakeLockManager();
wakeLockManager.onChange((event, data) => {
switch (event) {
case 'acquire':
console.log('Wake lock acquired');
break;
case 'release':
console.log('Wake lock released');
break;
case 'error':
console.error('Wake lock error:', data);
break;
}
});
// Request wake lock
wakeLockManager.request();
Auto-managed Wake Lock
class AutoWakeLock {
constructor(options = {}) {
this.manager = new WakeLockManager();
this.autoReacquire = options.autoReacquire !== false;
this.shouldBeActive = false;
if (this.autoReacquire) {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && this.shouldBeActive) {
this.manager.request();
}
});
}
}
async enable() {
this.shouldBeActive = true;
return await this.manager.request();
}
async disable() {
this.shouldBeActive = false;
return await this.manager.release();
}
get isActive() {
return this.manager.isActive;
}
get isSupported() {
return this.manager.isSupported;
}
}
// Usage
const autoWakeLock = new AutoWakeLock({ autoReacquire: true });
// Enable (will auto-reacquire when page becomes visible)
autoWakeLock.enable();
// Disable
// autoWakeLock.disable();
Practical Applications
Reading Mode
class ReadingMode {
constructor() {
this.wakeLock = new AutoWakeLock();
this.isEnabled = false;
this.scrollPosition = 0;
}
async enable() {
if (this.isEnabled) return;
this.isEnabled = true;
await this.wakeLock.enable();
// Save scroll position
this.scrollPosition = window.scrollY;
// Apply reading mode styles
document.body.classList.add('reading-mode');
// Hide unnecessary elements
this.hideElements();
console.log('Reading mode enabled');
}
async disable() {
if (!this.isEnabled) return;
this.isEnabled = false;
await this.wakeLock.disable();
// Remove reading mode styles
document.body.classList.remove('reading-mode');
// Restore elements
this.showElements();
console.log('Reading mode disabled');
}
hideElements() {
document.querySelectorAll('.hide-in-reading').forEach(el => {
el.dataset.originalDisplay = el.style.display;
el.style.display = 'none';
});
}
showElements() {
document.querySelectorAll('.hide-in-reading').forEach(el => {
el.style.display = el.dataset.originalDisplay || '';
});
}
toggle() {
if (this.isEnabled) {
return this.disable();
}
return this.enable();
}
}
// Usage
const readingMode = new ReadingMode();
document.getElementById('reading-mode-btn').addEventListener('click', () => {
readingMode.toggle();
});
Video Player
class VideoPlayerWithWakeLock {
constructor(videoElement) {
this.video = videoElement;
this.wakeLock = new AutoWakeLock();
this.setupEventListeners();
}
setupEventListeners() {
// Activate wake lock on play
this.video.addEventListener('play', () => {
this.enableWakeLock();
});
// Release on pause or end
this.video.addEventListener('pause', () => {
this.disableWakeLock();
});
this.video.addEventListener('ended', () => {
this.disableWakeLock();
});
// Ensure wake lock is active in fullscreen
document.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement && !this.video.paused) {
this.enableWakeLock();
}
});
}
async enableWakeLock() {
const success = await this.wakeLock.enable();
if (success) {
console.log('Video playing, screen will stay awake');
}
}
async disableWakeLock() {
await this.wakeLock.disable();
console.log('Video paused/ended, screen can sleep');
}
}
// Usage
const video = document.getElementById('video');
const player = new VideoPlayerWithWakeLock(video);
Navigation App
class NavigationApp {
constructor() {
this.wakeLock = new AutoWakeLock();
this.isNavigating = false;
this.route = null;
}
async startNavigation(route) {
this.route = route;
this.isNavigating = true;
// Enable wake lock
await this.wakeLock.enable();
// Start position tracking
this.watchId = navigator.geolocation.watchPosition(
(position) => {
this.updatePosition(position);
},
(error) => {
console.error('Position error:', error);
},
{
enableHighAccuracy: true,
maximumAge: 0
}
);
console.log('Navigation started, screen will stay awake');
}
async stopNavigation() {
this.isNavigating = false;
this.route = null;
// Stop position tracking
if (this.watchId) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
}
// Release wake lock
await this.wakeLock.disable();
console.log('Navigation ended');
}
updatePosition(position) {
if (!this.isNavigating) return;
const { latitude, longitude } = position.coords;
// Update map position
this.updateMap(latitude, longitude);
// Check if arrived at destination
if (this.hasArrived(latitude, longitude)) {
this.onArrival();
}
}
updateMap(lat, lng) {
// Update map display
console.log('Current position:', lat, lng);
}
hasArrived(lat, lng) {
if (!this.route?.destination) return false;
const distance = this.calculateDistance(
lat, lng,
this.route.destination.lat,
this.route.destination.lng
);
return distance < 50; // Within 50 meters is considered arrived
}
onArrival() {
console.log('You have arrived!');
this.stopNavigation();
// Play notification sound
this.playArrivalSound();
}
calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371e3;
const φ1 = lat1 * Math.PI / 180;
const φ2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
playArrivalSound() {
// Play arrival notification sound
}
}
// Usage
const navigation = new NavigationApp();
// Start navigation
navigation.startNavigation({
destination: { lat: 40.7128, lng: -74.0060 }
});
Presentation Mode
class PresentationMode {
constructor() {
this.wakeLock = new AutoWakeLock();
this.fullscreen = null;
this.isPresenting = false;
}
async start(element) {
if (this.isPresenting) return;
this.isPresenting = true;
// Activate wake lock
await this.wakeLock.enable();
// Enter fullscreen
if (element.requestFullscreen) {
await element.requestFullscreen();
}
// Hide mouse cursor
document.body.style.cursor = 'none';
// Listen for fullscreen exit
document.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
this.stop();
}
});
console.log('Presentation mode started');
}
async stop() {
if (!this.isPresenting) return;
this.isPresenting = false;
// Release wake lock
await this.wakeLock.disable();
// Exit fullscreen
if (document.fullscreenElement) {
await document.exitFullscreen();
}
// Restore mouse cursor
document.body.style.cursor = '';
console.log('Presentation mode ended');
}
}
// Usage
const presentation = new PresentationMode();
document.getElementById('present-btn').addEventListener('click', () => {
presentation.start(document.getElementById('slides'));
});
Timer Application
class TimerWithWakeLock {
constructor() {
this.wakeLock = new AutoWakeLock();
this.timerId = null;
this.remainingSeconds = 0;
this.onTick = null;
this.onComplete = null;
}
async start(seconds) {
if (this.timerId) {
this.stop();
}
this.remainingSeconds = seconds;
// Activate wake lock
await this.wakeLock.enable();
this.timerId = setInterval(() => {
this.tick();
}, 1000);
console.log('Timer started, screen will stay awake');
}
tick() {
this.remainingSeconds--;
this.onTick?.(this.remainingSeconds, this.formatTime(this.remainingSeconds));
if (this.remainingSeconds <= 0) {
this.complete();
}
}
async complete() {
this.stop();
// Release wake lock
await this.wakeLock.disable();
// Play alarm sound
this.playAlarm();
this.onComplete?.();
}
async stop() {
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = null;
}
await this.wakeLock.disable();
}
pause() {
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = null;
}
}
async resume() {
if (this.remainingSeconds > 0 && !this.timerId) {
await this.wakeLock.enable();
this.timerId = setInterval(() => {
this.tick();
}, 1000);
}
}
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
playAlarm() {
// Play alarm sound
const audio = new Audio('/sounds/alarm.mp3');
audio.play();
}
}
// Usage
const timer = new TimerWithWakeLock();
timer.onTick = (remaining, formatted) => {
document.getElementById('timer-display').textContent = formatted;
};
timer.onComplete = () => {
alert('Time is up!');
};
// Start 5 minute countdown
timer.start(300);
Best Practices Summary
Screen Wake Lock API Best Practices:
┌─────────────────────────────────────────────────────┐
│ │
│ Use Cases │
│ ├── Video/audio playback │
│ ├── Navigation apps │
│ ├── Reading mode │
│ ├── Timers/alarms │
│ └── Presentations/slideshows │
│ │
│ Battery Management │
│ ├── Only request when needed │
│ ├── Release immediately when not needed │
│ ├── Handle release events for edge cases │
│ └── Auto-release when page hidden │
│ │
│ User Experience │
│ ├── Inform user screen stays awake │
│ ├── Provide manual control option │
│ ├── Auto-reacquire when page visible │
│ └── Graceful fallback for unsupported browsers │
│ │
└─────────────────────────────────────────────────────┘
| Scenario | Need Wake Lock? |
|---|---|
| Video playback | Yes |
| Music playback (background) | No (audio already prevents sleep) |
| Reading long articles | Yes |
| Map navigation | Yes |
| Normal browsing | No |
Use the Screen Wake Lock API wisely to keep the screen awake when needed.