Introduction
The web platform ships with dozens of powerful, standardized APIs that most developers never discover. Instead of reaching for an npm package to handle element visibility, file access, clipboard operations, or screen wake locks, you can use built-in browser APIs that are faster, smaller, and maintained by browser vendors. These APIs solve real production problems — lazy loading images, reading local files, sharing content natively, preventing screen dimming during presentations, and coordinating state across browser tabs — all without a single external dependency.
This guide dives deep into the most impactful lesser-known Web APIs. Each section includes production-ready code, browser compatibility notes, and real-world use cases. By the end, you'll eliminate several npm packages from your bundle and build more capable web applications.
IntersectionObserver: Visibility Detection at Scale
IntersectionObserver efficiently detects when elements enter or exit the viewport. Unlike scroll event listeners that fire dozens of times per second and require expensive getBoundingClientRect() calls, IntersectionObserver uses an optimized browser-native mechanism that runs off the main thread.
How It Works Internally
The browser maintains a list of observed elements and their thresholds. When the user scrolls or the layout changes, the compositor thread (not the main thread) calculates which elements intersect with the root (viewport or a parent element). When a threshold is crossed, the callback is queued on the main thread. This design means zero scroll event overhead and no forced layout reflows.
Lazy Loading Images
const imageObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.srcset = img.dataset.srcset || '';
img.classList.add('loaded');
imageObserver.unobserve(img);
}
});
},
{
rootMargin: '200px 0px',
threshold: 0.01,
}
);
document.querySelectorAll('img[data-src]').forEach((img) => {
imageObserver.observe(img);
});The rootMargin: '200px 0px' parameter tells the browser to start loading images 200 pixels before they become visible, eliminating the flash of unloaded content users see with naive implementations.
Infinite Scroll Pattern
const sentinel = document.querySelector('#sentinel');
const list = document.querySelector('#list');
let isLoading = false;
const scrollObserver = new IntersectionObserver(async (entries) => {
if (entries[0].isIntersecting && !isLoading) {
isLoading = true;
const items = await fetchNextPage();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item.name;
list.appendChild(li);
});
isLoading = false;
}
}, { rootMargin: '400px' });
scrollObserver.observe(sentinel);Scroll-Based Animations
const animationObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
}
});
},
{ threshold: 0.15 }
);
document.querySelectorAll('.reveal').forEach((el) => {
animationObserver.observe(el);
});Browser support: Chrome 58+, Firefox 55+, Safari 12.1+, Edge 15+. Fully supported in all modern browsers.
ResizeObserver: Element-Level Size Monitoring
ResizeObserver watches individual elements for size changes — a massive improvement over listening to window.resize events, which only tell you the viewport changed, not which specific elements changed size.
Responsive Components Without Media Queries
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
const element = entry.target;
// Container queries before CSS container queries existed
if (width < 300) {
element.classList.add('compact');
element.classList.remove('default', 'wide');
} else if (width < 600) {
element.classList.add('default');
element.classList.remove('compact', 'wide');
} else {
element.classList.add('wide');
element.classList.remove('compact', 'default');
}
}
});
document.querySelectorAll('[data-responsive]').forEach((el) => {
resizeObserver.observe(el);
});Dynamic Chart Resizing
const chartObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
const chart = chartInstances.get(entry.target.id);
if (chart) {
chart.resize(width, height);
}
}
});
document.querySelectorAll('.chart-container').forEach((container) => {
chartObserver.observe(container);
});Responsive Text Sizing
const textObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const { width } = entry.contentRect;
const fontSize = Math.max(14, Math.min(24, width / 20));
entry.target.style.fontSize = `${fontSize}px`;
});
});Browser support: Chrome 64+, Firefox 69+, Safari 13.1+, Edge 79+. The devicePixelContentBoxSize property is newer (Chrome 84+).
Web Animations API: JavaScript-Native Animation Control
The Web Animations API (WAAPI) provides a JavaScript interface to CSS animations with full control over playback, timing, and composition. It's supported in every modern browser and eliminates the need for animation libraries like GSAP for most use cases.
Basic Animation
const element = document.querySelector('.box');
const animation = element.animate(
[
{ transform: 'translateX(0)', opacity: 1 },
{ transform: 'translateX(100px)', opacity: 0.5 },
{ transform: 'translateX(200px)', opacity: 1 },
],
{
duration: 1000,
easing: 'ease-in-out',
fill: 'forwards',
iterations: 2,
}
);
// Full playback control
animation.pause();
animation.play();
animation.reverse();
animation.cancel();
// Await completion
await animation.finished;
console.log('Animation done');Staggered List Animations
function staggerAnimation(elements, delay = 100) {
return Promise.all(
elements.map((el, index) =>
el.animate(
[
{ opacity: 0, transform: 'translateY(20px)' },
{ opacity: 1, transform: 'translateY(0)' },
],
{
duration: 300,
delay: index * delay,
fill: 'forwards',
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
}
).finished
)
);
}
// Usage
await staggerAnimation(document.querySelectorAll('.list-item'), 50);Spring-Like Animation
function springAnimate(element, property, from, to) {
return element.animate(
[
{ [property]: from },
{ [property]: to },
],
{
duration: 600,
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
fill: 'forwards',
}
).finished;
}Scroll-Triggered Parallax
function createParallax(element, speed = 0.5) {
const animation = element.animate(
{ transform: ['translateY(0)', 'translateY(-100px)'] },
{ duration: 1000, fill: 'forwards' }
);
animation.pause();
window.addEventListener('scroll', () => {
const scrolled = window.scrollY;
const rate = scrolled * speed;
animation.currentTime = rate;
}, { passive: true });
}Browser support: Chrome 36+, Firefox 48+, Safari 13.1+, Edge 79+. The Animation.commitStyles() method is newer (Chrome 84+).
Navigation API: The Future of Client-Side Routing
The Navigation API replaces the aging History API with a cleaner, more capable interface. It's the foundation for modern SPA routing and is already used by frameworks like Next.js App Router under the hood.
Intercepting Navigation
navigation.addEventListener('navigate', (event) => {
const url = new URL(event.destination.url);
if (url.pathname.startsWith('/app')) {
event.intercept({
async handler() {
const content = await loadRoute(url.pathname);
document.querySelector('#app').innerHTML = content;
updateActiveLink(url.pathname);
},
focusReset: 'after-transition',
scroll: 'after-transition',
});
}
});Programmatic Navigation with State
navigation.navigate('/dashboard', {
state: { from: 'home', timestamp: Date.now() },
info: { transition: 'slide' },
});
navigation.back();
// Read current state
const state = navigation.currentEntry.getState();
console.log(state.from); // 'home'Navigation Confirmation Dialog
navigation.addEventListener('navigate', (event) => {
if (hasUnsavedChanges && !event.canIntercept) {
event.preventDefault();
showSaveDialog().then((confirmed) => {
if (confirmed) {
navigation.navigate(event.destination.url);
}
});
}
});Route Transition with View Transitions API
navigation.addEventListener('navigate', (event) => {
event.intercept({
async handler() {
const transition = document.startViewTransition(async () => {
await renderNewPage();
});
await transition.finished;
},
});
});Browser support: Chrome 102+, Edge 102+. Not yet in Firefox or Safari (as of early 2025). Use the History API as a fallback.
Clipboard API: Async Copy and Paste
The modern Clipboard API replaces the deprecated document.execCommand('copy') with an asynchronous, promise-based interface that supports both text and rich content.
Reading from the Clipboard
async function readClipboard() {
try {
const permission = await navigator.permissions.query({
name: 'clipboard-read'
});
if (permission.state === 'denied') {
throw new Error('Clipboard permission denied');
}
const text = await navigator.clipboard.readText();
console.log('Clipboard content:', text);
return text;
} catch (err) {
console.error('Failed to read clipboard:', err);
}
}Writing to the Clipboard
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
showToast('Copied to clipboard!');
} catch (err) {
// Fallback for older browsers
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
}Reading Images from Clipboard
async function readClipboardImage() {
const items = await navigator.clipboard.read();
for (const item of items) {
for (const type of item.types) {
if (type.startsWith('image/')) {
const blob = await item.getType(type);
const img = document.createElement('img');
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
return blob;
}
}
}
}Browser support: Chrome 76+, Firefox 127+, Safari 13.1+, Edge 79+. Requires a secure context (HTTPS) and user activation.
Web Share API: Native Sharing
The Web Share API triggers the operating system's native share sheet, letting users share content to any installed app — messaging, email, social media, notes, and more.
async function shareContent(title, text, url) {
if (!navigator.share) {
// Fallback: copy link to clipboard
await navigator.clipboard.writeText(url);
showToast('Link copied to clipboard');
return;
}
try {
await navigator.share({ title, text, url });
console.log('Shared successfully');
} catch (err) {
if (err.name !== 'AbortError') {
console.error('Share failed:', err);
}
}
}Sharing Files
async function shareFile(file) {
if (navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({
files: [file],
title: 'Shared file',
text: 'Check out this file',
});
}
}Browser support: Chrome 89+, Safari 12.1+, Edge 93+. Not available on Firefox desktop (works on Firefox Android).
File System Access API: Local File Operations
The File System Access API lets web applications read and write files on the user's local file system with explicit permission. This enables web-based code editors, image editors, and document processors that rival desktop applications.
Reading a File
async function openFile() {
try {
const [fileHandle] = await window.showOpenFilePicker({
types: [{
description: 'JavaScript files',
accept: { 'text/javascript': ['.js', '.ts'] },
}],
});
const file = await fileHandle.getFile();
const contents = await file.text();
return { name: file.name, contents };
} catch (err) {
if (err.name !== 'AbortError') {
console.error('Error opening file:', err);
}
}
}Writing to a File
async function saveFile(content) {
const fileHandle = await window.showSaveFilePicker({
types: [{
description: 'Markdown files',
accept: { 'text/markdown': ['.md'] },
}],
});
const writable = await fileHandle.createWritable();
await writable.write(content);
await writable.close();
}Directory Access
async function readDirectory() {
const dirHandle = await window.showDirectoryPicker();
const files = [];
for await (const entry of dirHandle.values()) {
if (entry.kind === 'file') {
const file = await entry.getFile();
files.push({
name: entry.name,
size: file.size,
lastModified: file.lastModified,
});
}
}
return files;
}Saving to a Previously Opened File
// Store the handle for later reuse
let savedFileHandle = null;
async function openAndRemember() {
const [handle] = await window.showOpenFilePicker();
savedFileHandle = handle;
}
async function saveToRemembered(content) {
if (!savedFileHandle) return;
// Verify permission
const permission = await savedFileHandle.requestPermission({ mode: 'readwrite' });
if (permission !== 'granted') return;
const writable = await savedFileHandle.createWritable();
await writable.write(content);
await writable.close();
}Browser support: Chrome 86+, Edge 86+. Not supported in Firefox or Safari. Use the Origin Private File System as a cross-browser alternative for storage.
Screen Wake Lock API: Keep the Display On
The Screen Wake Lock API prevents the device screen from dimming or locking while your application needs to remain visible. Essential for dashboards, navigation apps, fitness trackers, and presentation tools.
let wakeLock = null;
async function requestWakeLock() {
try {
wakeLock = await navigator.wakeLock.request('screen');
console.log('Wake lock acquired');
wakeLock.addEventListener('release', () => {
console.log('Wake lock released');
});
} catch (err) {
console.error(`${err.name}: ${err.message}`);
}
}
// Re-acquire when page becomes visible again
document.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'visible') {
await requestWakeLock();
}
});
function releaseWakeLock() {
if (wakeLock) {
wakeLock.release();
wakeLock = null;
}
}Browser support: Chrome 84+, Edge 84+, Safari 16.4+, Firefox 129+. Requires a secure context.
Idle Detection API: Detect User Inactivity
The Idle Detection API lets your application know when the user is idle or has switched to a different screen. Perfect for pausing expensive operations, showing status updates, or triggering background sync.
async function startIdleDetection() {
if (!('IdleDetector' in window)) {
console.log('Idle Detection API not supported');
return;
}
const permission = await IdleDetector.requestPermission();
if (permission !== 'granted') {
console.log('Permission denied');
return;
}
const detector = new IdleDetector();
detector.addEventListener('change', () => {
const { userState, screenState } = detector;
console.log(`User: ${userState}, Screen: ${screenState}`);
if (userState === 'idle' || screenState === 'locked') {
pauseExpensiveOperations();
} else {
resumeExpensiveOperations();
}
});
await detector.start({
threshold: 60000, // 1 minute
});
}Browser support: Chrome 94+, Edge 94+. Not supported in Firefox or Safari. Requires a secure context and the idle-detection permission.
Compression Streams API: Client-Side Compression
The Compression Streams API provides built-in gzip, deflate, and deflate-raw compression using the Streams API. No more shipping pako or fflate to the client.
Compressing a File Before Upload
async function compressAndUpload(file) {
const stream = file.stream();
const compressedStream = stream.pipeThrough(
new CompressionStream('gzip')
);
const compressedBlob = await new Response(compressedStream).blob();
const formData = new FormData();
formData.append('file', compressedBlob, file.name + '.gz');
formData.append('originalSize', file.size);
formData.append('compressedSize', compressedBlob.size);
await fetch('/api/upload', { method: 'POST', body: formData });
}Decompressing Server Responses
async function fetchCompressedData(url) {
const response = await fetch(url);
const decompressedStream = response.body.pipeThrough(
new DecompressionStream('gzip')
);
const text = await new Response(decompressedStream).text();
return JSON.parse(text);
}Compression in a Web Worker
// worker.js
self.onmessage = async (event) => {
const { data, id } = event.data;
const stream = new Blob([data]).stream();
const compressed = stream.pipeThrough(new CompressionStream('gzip'));
const blob = await new Response(compressed).blob();
self.postMessage({ id, result: blob });
};Browser support: Chrome 80+, Firefox 113+, Safari 16.4+, Edge 80+. Widely supported.
BroadcastChannel API: Cross-Tab Communication
The BroadcastChannel API enables messaging between tabs, windows, and workers on the same origin — no server required.
Logout Sync Across Tabs
// Tab 1: User logs out
const channel = new BroadcastChannel('auth');
channel.postMessage({ type: 'logout', userId: '123' });
// Tab 2: Receive logout
const channel = new BroadcastChannel('auth');
channel.addEventListener('message', (event) => {
if (event.data.type === 'logout') {
clearSession();
redirect('/login');
}
});Shared Notification State
const channel = new BroadcastChannel('notifications');
// Mark as read in one tab, update all others
function markAsRead(notificationId) {
updateLocalState(notificationId);
channel.postMessage({
type: 'notification-read',
id: notificationId,
});
}
channel.addEventListener('message', (event) => {
if (event.data.type === 'notification-read') {
updateLocalState(event.data.id);
}
});Browser support: Chrome 54+, Firefox 38+, Safari 15.4+, Edge 79+. Well supported.
DocumentPictureInPicture API: Arbitrary HTML in PiP
The Document Picture-in-Picture API opens a floating window with any HTML content — not just video. This enables persistent timers, chat widgets, music controls, and dashboards that stay visible regardless of which tab the user is viewing.
async function openPiP() {
if (!('documentPictureInPicture' in window)) return;
const pipWindow = await documentPictureInPicture.requestWindow({
width: 400,
height: 300,
});
pipWindow.document.body.innerHTML = `
<style>
body { font-family: system-ui; padding: 16px; margin: 0; }
.timer { font-size: 48px; font-weight: bold; text-align: center; }
.controls { display: flex; gap: 8px; justify-content: center; margin-top: 16px; }
</style>
<div class="timer" id="timer">00:00</div>
<div class="controls">
<button id="start">Start</button>
<button id="reset">Reset</button>
</div>
`;
const timerEl = pipWindow.document.getElementById('timer');
let seconds = 0;
let interval = null;
pipWindow.document.getElementById('start').onclick = () => {
if (interval) {
clearInterval(interval);
interval = null;
} else {
interval = setInterval(() => {
seconds++;
timerEl.textContent = new Date(seconds * 1000)
.toISOString().substr(14, 5);
}, 1000);
}
};
pipWindow.document.getElementById('reset').onclick = () => {
clearInterval(interval);
interval = null;
seconds = 0;
timerEl.textContent = '00:00';
};
pipWindow.addEventListener('pagehide', () => clearInterval(interval));
}Browser support: Chrome 116+, Edge 116+. Not yet in Firefox or Safari.
Scheduler API: Prioritized Task Scheduling
The Scheduler API helps break up long tasks to keep the main thread responsive. It replaces the hack of wrapping work in setTimeout(0) with proper priority levels.
async function processItems(items) {
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
// Yield to the main thread every 50 items
if (i % 50 === 0 && 'scheduler' in globalThis) {
await scheduler.yield();
}
}
}
// Post tasks with explicit priorities
if ('scheduler' in globalThis) {
scheduler.postTask(() => updateUI(), {
priority: 'user-blocking',
});
scheduler.postTask(() => prefetchData(), {
priority: 'background',
});
scheduler.postTask(() => sendAnalytics(), {
priority: 'background',
delay: 5000,
});
}Browser support: Chrome 94+, Edge 94+. Not yet in Firefox or Safari. Use requestIdleCallback or setTimeout as fallbacks.
View Transitions API: Smooth Page Transitions
The View Transitions API creates smooth animated transitions between DOM states — both within a single page and during cross-document navigations.
async function switchView(newContent) {
if (!document.startViewTransition) {
updateDOM(newContent);
return;
}
const transition = document.startViewTransition(() => {
updateDOM(newContent);
});
await transition.finished;
}/* Customize transition animations */
::view-transition-old(root) {
animation: fade-out 0.3s ease-out;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease-in;
}
/* Named transitions for specific elements */
.hero-image {
view-transition-name: hero;
}
::view-transition-old(hero) {
animation: slide-out 0.3s ease-out;
}
::view-transition-new(hero) {
animation: slide-in 0.3s ease-in;
}Cross-Document Transitions (MPA)
/* In your CSS — no JavaScript needed for MPA transitions */
@view-transition {
navigation: auto;
}Browser support: Chrome 111+, Edge 111+, Safari 18+. Not yet in Firefox.
Web Locks API: Cross-Tab Coordination
The Web Locks API coordinates access to shared resources across tabs, workers, and iframes on the same origin. It prevents race conditions in multi-tab applications.
// Safe database migration that prevents concurrent access
async function migrateDatabase() {
await navigator.locks.request('db-migration', async (lock) => {
// Only one tab can hold this lock at a time
console.log('Migration started');
await runDatabaseMigration();
console.log('Migration complete');
});
}
// Other tabs that need the database wait for the lock
async function readFromDatabase(query) {
return navigator.locks.request(
'db-migration',
{ mode: 'shared' },
async () => {
return executeQuery(query);
}
);
}Leader Election Pattern
async function electLeader(callback) {
const leader = await navigator.locks.request(
'leader-lock',
{ mode: 'exclusive', ifAvailable: true },
async (lock) => {
if (!lock) return false; // Another tab is leader
// This tab is the leader
await new Promise((resolve) => {
// Keep the lock held indefinitely
callback(resolve);
});
return true;
}
);
return leader;
}Browser support: Chrome 69+, Firefox 96+, Safari 15.4+, Edge 79+. Well supported.
EyeDropper API: Color Picker from Screen
The EyeDropper API lets users pick any color from their screen — pixels from any application, not just your web page.
async function pickColor() {
if (!('EyeDropper' in window)) {
console.log('EyeDropper not supported');
return null;
}
const eyeDropper = new EyeDropper();
try {
const result = await eyeDropper.open();
console.log('Selected color:', result.sRGBHex);
return result.sRGBHex;
} catch (err) {
// User pressed Escape
return null;
}
}
// Usage with a button
document.getElementById('pick-color').addEventListener('click', async () => {
const color = await pickColor();
if (color) {
document.getElementById('color-preview').style.backgroundColor = color;
document.getElementById('color-hex').textContent = color;
}
});Browser support: Chrome 95+, Edge 95+. Not supported in Firefox or Safari.
Speculation Rules API: Predictive Prefetching
The Speculation Rules API tells the browser which pages the user is likely to visit next, enabling instant page loads through prefetching and prerendering.
<script type="speculationrules">
{
"prerender": [
{
"where": {
"href_matches": "/blog/*"
},
"eagerness": "moderate"
}
],
"prefetch": [
{
"where": {
"href_matches": "/products/*"
},
"eagerness": "conservative"
}
]
}
</script>Dynamic Speculation Rules
function addSpeculationRules(urls) {
const rules = {
prerender: urls.map(url => ({
source: 'list',
urls: [url],
})),
};
const script = document.createElement('script');
script.type = 'speculationrules';
script.textContent = JSON.stringify(rules);
document.head.appendChild(script);
}
// Prerender pages the user is likely to visit
addSpeculationRules(['/dashboard', '/settings', '/profile']);Browser support: Chrome 109+, Edge 109+. Not in Firefox or Safari. Use <link rel="prefetch"> as a fallback.
Compression Streams + File System Access: A Powerful Combination
Combining APIs multiplies their value. Here's a pattern that compresses a directory of files and saves the result — entirely client-side:
async function compressDirectory() {
const dirHandle = await window.showDirectoryPicker();
const files = [];
for await (const entry of dirHandle.values()) {
if (entry.kind === 'file') {
const file = await entry.getFile();
const compressed = file.stream().pipeThrough(
new CompressionStream('gzip')
);
const blob = await new Response(compressed).blob();
files.push({ name: entry.name, data: blob });
}
}
// Save compressed archive
const fileHandle = await window.showSaveFilePicker({
types: [{ description: 'Gzipped files', accept: { 'application/gzip': ['.gz'] } }],
});
const writable = await fileHandle.createWritable();
for (const file of files) {
await writable.write(file.data);
}
await writable.close();
}Browser Compatibility Summary
| API | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| IntersectionObserver | 58+ | 55+ | 12.1+ | 15+ |
| ResizeObserver | 64+ | 69+ | 13.1+ | 79+ |
| Web Animations | 36+ | 48+ | 13.1+ | 79+ |
| Clipboard API | 76+ | 127+ | 13.1+ | 79+ |
| Web Share | 89+ | — | 12.1+ | 93+ |
| File System Access | 86+ | — | — | 86+ |
| Screen Wake Lock | 84+ | 129+ | 16.4+ | 84+ |
| Idle Detection | 94+ | — | — | 94+ |
| Compression Streams | 80+ | 113+ | 16.4+ | 80+ |
| BroadcastChannel | 54+ | 38+ | 15.4+ | 79+ |
| Document PiP | 116+ | — | — | 116+ |
| Navigation API | 102+ | — | — | 102+ |
| View Transitions | 111+ | — | 18+ | 111+ |
| Web Locks | 69+ | 96+ | 15.4+ | 79+ |
| EyeDropper | 95+ | — | — | 95+ |
| Scheduler API | 94+ | — | — | 94+ |
| Speculation Rules | 109+ | — | — | 109+ |
Best Practices
- Always feature-detect before using an API:
'IntersectionObserver' in windoworif (!navigator.share) return - Provide graceful fallbacks for APIs with limited browser support
- Use observers over event listeners — IntersectionObserver and ResizeObserver are designed for performance
- Unobserve when done — clean up observers when elements are removed from the DOM
- Prefer progressive enhancement — build a working experience first, then enhance with advanced APIs
- Test across browsers — many newer APIs are Chromium-only; always provide fallbacks
- Use HTTPS — most modern APIs require a secure context
Common Pitfalls
| Pitfall | Impact | Solution |
|---|---|---|
| No cleanup for observers | Memory leaks | Call unobserve() or disconnect() when elements are removed |
| Using scroll events for visibility | Poor performance | Use IntersectionObserver |
window.resize for element size | Incorrect measurements | Use ResizeObserver |
| No feature detection | Crashes in older browsers | Always check for API support |
Using document.execCommand('copy') | Deprecated, unreliable | Use the Clipboard API |
Not handling AbortError from Share API | Console errors | Catch and ignore AbortError (user cancelled) |
| Requesting wake lock without re-acquiring on visibility change | Lock lost on tab switch | Re-acquire in visibilitychange handler |
Conclusion
The web platform provides powerful, standardized APIs that solve common problems without external libraries. By learning and adopting these APIs, you can:
- Reduce bundle size by removing polyfills and utility libraries
- Improve performance by using browser-native implementations
- Enhance user experience with native-feeling interactions
- Future-proof your code by building on standards rather than library-specific abstractions
Start with IntersectionObserver, ResizeObserver, and the Clipboard API — they have the broadest support and solve the most common problems. Then explore the newer APIs (Navigation API, View Transitions, Speculation Rules) as browser support expands. The web platform is more capable than ever — take advantage of what's already built in.