MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Web Platform APIs You Might Not Know

Explore underused Web APIs: ResizeObserver, IntersectionObserver, Web Animations, Navigation API.

Web APIsJavaScriptFrontendBrowser

By MinhVo

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.

Exploring the web platform's hidden capabilities

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.

Performance optimization through browser APIs

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+).

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.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.

Modern web development APIs

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.

Browser APIs for modern applications

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

APIChromeFirefoxSafariEdge
IntersectionObserver58+55+12.1+15+
ResizeObserver64+69+13.1+79+
Web Animations36+48+13.1+79+
Clipboard API76+127+13.1+79+
Web Share89+—12.1+93+
File System Access86+——86+
Screen Wake Lock84+129+16.4+84+
Idle Detection94+——94+
Compression Streams80+113+16.4+80+
BroadcastChannel54+38+15.4+79+
Document PiP116+——116+
Navigation API102+——102+
View Transitions111+—18+111+
Web Locks69+96+15.4+79+
EyeDropper95+——95+
Scheduler API94+——94+
Speculation Rules109+——109+

Best Practices

  1. Always feature-detect before using an API: 'IntersectionObserver' in window or if (!navigator.share) return
  2. Provide graceful fallbacks for APIs with limited browser support
  3. Use observers over event listeners — IntersectionObserver and ResizeObserver are designed for performance
  4. Unobserve when done — clean up observers when elements are removed from the DOM
  5. Prefer progressive enhancement — build a working experience first, then enhance with advanced APIs
  6. Test across browsers — many newer APIs are Chromium-only; always provide fallbacks
  7. Use HTTPS — most modern APIs require a secure context

Common Pitfalls

PitfallImpactSolution
No cleanup for observersMemory leaksCall unobserve() or disconnect() when elements are removed
Using scroll events for visibilityPoor performanceUse IntersectionObserver
window.resize for element sizeIncorrect measurementsUse ResizeObserver
No feature detectionCrashes in older browsersAlways check for API support
Using document.execCommand('copy')Deprecated, unreliableUse the Clipboard API
Not handling AbortError from Share APIConsole errorsCatch and ignore AbortError (user cancelled)
Requesting wake lock without re-acquiring on visibility changeLock lost on tab switchRe-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.