Progressive Web Apps (PWA)
Web APIs for PWAs
Web APIs for PWAs
Progressive Web Apps gain native-like capabilities through powerful Web APIs. This lesson explores modern browser APIs that enhance PWA functionality with device features like location, camera, sharing, and more.
Geolocation API
Access the user's geographical location with permission-based geolocation features.
<!-- Geolocation in PWA -->
<button id="getLocation">Get My Location</button>
<div id="locationResult"></div>
<script>
document.getElementById('getLocation').addEventListener('click', () => {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const accuracy = position.coords.accuracy;
document.getElementById('locationResult').innerHTML = `
<p>Latitude: ${lat}</p>
<p>Longitude: ${lon}</p>
<p>Accuracy: ${accuracy} meters</p>
`;
},
(error) => {
console.error('Geolocation error:', error.message);
alert('Unable to retrieve location');
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
}
);
} else {
alert('Geolocation is not supported');
}
});
// Watch position continuously
const watchId = navigator.geolocation.watchPosition(
(position) => {
console.log('Position updated:', position.coords);
},
(error) => console.error(error),
{ enableHighAccuracy: true }
);
// Stop watching
// navigator.geolocation.clearWatch(watchId);
</script>
Tip: Always provide fallback behavior when geolocation is denied or unavailable. Consider user privacy and only request location when necessary.
Camera and MediaDevices API
Capture photos and video using the device camera directly in your PWA.
<!-- Camera Access -->
<button id="startCamera">Start Camera</button>
<button id="takePhoto">Take Photo</button>
<video id="video" autoplay playsinline></video>
<canvas id="canvas"></canvas>
<img id="photo" alt="Captured photo">
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const photo = document.getElementById('photo');
let stream = null;
// Start camera
document.getElementById('startCamera').addEventListener('click', async () => {
try {
stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'user', // 'environment' for back camera
width: { ideal: 1280 },
height: { ideal: 720 }
},
audio: false
});
video.srcObject = stream;
} catch (error) {
console.error('Camera access error:', error);
alert('Unable to access camera');
}
});
// Take photo
document.getElementById('takePhoto').addEventListener('click', () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
// Convert to image
const imageData = canvas.toDataURL('image/png');
photo.src = imageData;
// Stop camera
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
});
// Check available devices
navigator.mediaDevices.enumerateDevices()
.then(devices => {
const cameras = devices.filter(d => d.kind === 'videoinput');
console.log('Available cameras:', cameras.length);
});
</script>
Note: Camera access requires HTTPS in production. Some browsers require user interaction (button click) before requesting permissions.
Web Share API
Enable native sharing functionality to share content from your PWA to other apps.
<!-- Web Share API -->
<button id="shareButton">Share This Article</button>
<script>
const shareButton = document.getElementById('shareButton');
if (navigator.share) {
shareButton.addEventListener('click', async () => {
try {
await navigator.share({
title: 'Amazing PWA Tutorial',
text: 'Check out this comprehensive PWA guide!',
url: window.location.href
});
console.log('Content shared successfully');
} catch (error) {
console.error('Share failed:', error);
}
});
} else {
// Fallback for browsers without Web Share
shareButton.addEventListener('click', () => {
const shareUrl = `mailto:?subject=Check this out&body=${encodeURIComponent(window.location.href)}`;
window.location.href = shareUrl;
});
}
// Share files (images, PDFs, etc.)
async function shareFile(file) {
if (navigator.canShare && navigator.canShare({ files: [file] })) {
try {
await navigator.share({
files: [file],
title: 'Shared File',
text: 'Check out this file!'
});
} catch (error) {
console.error('File share failed:', error);
}
} else {
console.log('File sharing not supported');
}
}
</script>
Vibration API
Provide haptic feedback to enhance user interactions.
<script>
// Single vibration (200ms)
if ('vibrate' in navigator) {
navigator.vibrate(200);
}
// Vibration pattern: vibrate 100ms, pause 50ms, vibrate 100ms
navigator.vibrate([100, 50, 100]);
// Stop vibration
navigator.vibrate(0);
// Button with haptic feedback
document.getElementById('actionButton').addEventListener('click', () => {
navigator.vibrate(50); // Short vibration on click
// Perform action...
});
// Success vibration pattern
function successVibration() {
navigator.vibrate([50, 50, 50]);
}
// Error vibration pattern
function errorVibration() {
navigator.vibrate([200, 100, 200]);
}
</script>
Warning: Use vibration sparingly. Excessive vibrations can be annoying and drain battery. Always respect user preferences and accessibility settings.
Screen Wake Lock API
Keep the screen awake during important tasks like reading or navigation.
<button id="toggleWakeLock">Keep Screen Awake</button>
<script>
let wakeLock = null;
async function requestWakeLock() {
try {
wakeLock = await navigator.wakeLock.request('screen');
console.log('Screen wake lock activated');
wakeLock.addEventListener('release', () => {
console.log('Screen wake lock released');
});
} catch (error) {
console.error('Wake lock request failed:', error);
}
}
async function releaseWakeLock() {
if (wakeLock !== null) {
await wakeLock.release();
wakeLock = null;
}
}
document.getElementById('toggleWakeLock').addEventListener('click', () => {
if (wakeLock === null) {
requestWakeLock();
} else {
releaseWakeLock();
}
});
// Re-request wake lock when page becomes visible
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && wakeLock !== null) {
requestWakeLock();
}
});
</script>
Device Orientation API
Access device motion and orientation data for interactive experiences.
<script>
// Device orientation (alpha, beta, gamma)
window.addEventListener('deviceorientation', (event) => {
const alpha = event.alpha; // 0-360 degrees (compass)
const beta = event.beta; // -180 to 180 degrees (front-to-back tilt)
const gamma = event.gamma; // -90 to 90 degrees (left-to-right tilt)
console.log(`Orientation: α=${alpha}° β=${beta}° γ=${gamma}°`);
});
// Device motion (acceleration)
window.addEventListener('devicemotion', (event) => {
const acceleration = event.acceleration;
const rotationRate = event.rotationRate;
console.log('Acceleration:', {
x: acceleration.x,
y: acceleration.y,
z: acceleration.z
});
});
// Request permission (iOS 13+)
if (typeof DeviceOrientationEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission()
.then(permissionState => {
if (permissionState === 'granted') {
window.addEventListener('deviceorientation', handleOrientation);
}
})
.catch(console.error);
}
</script>
Clipboard API
Read from and write to the system clipboard securely.
<button id="copyButton">Copy Text</button>
<button id="pasteButton">Paste Text</button>
<textarea id="clipboardText"></textarea>
<script>
// Copy to clipboard
document.getElementById('copyButton').addEventListener('click', async () => {
const text = document.getElementById('clipboardText').value;
try {
await navigator.clipboard.writeText(text);
console.log('Text copied to clipboard');
} catch (error) {
console.error('Copy failed:', error);
// Fallback for older browsers
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
});
// Paste from clipboard
document.getElementById('pasteButton').addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText();
document.getElementById('clipboardText').value = text;
console.log('Text pasted from clipboard');
} catch (error) {
console.error('Paste failed:', error);
}
});
// Copy images
async function copyImage(blob) {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
console.log('Image copied');
} catch (error) {
console.error('Image copy failed:', error);
}
}
</script>
Tip: Clipboard operations require user interaction and HTTPS. Always handle permissions gracefully and provide feedback to users.
Exercise:
- Create a PWA that uses Geolocation API to show the user's current position on a map
- Implement a camera feature that takes a photo and allows sharing via Web Share API
- Add haptic feedback (vibration) to key user interactions in your PWA
- Implement a reading mode that uses Screen Wake Lock to keep the screen awake
- Create a note-taking PWA with clipboard copy/paste functionality
Best Practices
- Permission Handling: Always check for API support and handle permission denials gracefully
- Progressive Enhancement: Provide fallbacks for browsers that don't support specific APIs
- User Privacy: Be transparent about why you need device permissions
- Battery Efficiency: Use Wake Lock and device sensors responsibly to avoid battery drain
- Error Handling: Implement robust error handling for all Web API calls
- Feature Detection: Use feature detection instead of browser detection