Back to blog

Modern Browser Web APIs Every JavaScript Developer Should Know

Hello HaWkers, modern browsers offer an impressive arsenal of native APIs that many developers are unaware of. These APIs can replace heavy libraries and add advanced functionality to your applications.

Did you know it's possible to detect when elements enter the viewport, execute code in separate threads, or access the local file system, all with native JavaScript?

Why Use Native Browser APIs

Before installing yet another dependency, consider what the browser already offers for free.

Advantages of Native APIs

Performance:

  • Zero additional download for the user
  • Implementations optimized in native code
  • Smaller bundle size

Maintenance:

  • No dependencies to update
  • APIs standardized by W3C
  • Official documentation on MDN

Compatibility:

  • Most modern APIs have 95%+ browser support
  • Polyfills available when needed

Intersection Observer API

This API allows detecting when elements enter or leave the viewport extremely efficiently.

Lazy Loading Images

// Native lazy loading implementation
class ImageLazyLoader {
  constructor(options = {}) {
    this.options = {
      rootMargin: '50px 0px',
      threshold: 0.01,
      ...options
    };

    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      this.options
    );
  }

  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        const src = img.dataset.src;
        const srcset = img.dataset.srcset;

        if (src) {
          img.src = src;
          img.removeAttribute('data-src');
        }

        if (srcset) {
          img.srcset = srcset;
          img.removeAttribute('data-srcset');
        }

        img.classList.add('loaded');
        this.observer.unobserve(img);
      }
    });
  }

  observe(selector = 'img[data-src]') {
    const images = document.querySelectorAll(selector);
    images.forEach(img => this.observer.observe(img));
  }

  disconnect() {
    this.observer.disconnect();
  }
}

// Usage
const lazyLoader = new ImageLazyLoader();
lazyLoader.observe();

Scroll Animations (Scroll Reveal)

// Revealing elements on scroll
function initScrollReveal() {
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.classList.add('visible');

          // Delay based on data-delay
          const delay = entry.target.dataset.delay || 0;
          entry.target.style.transitionDelay = `${delay}ms`;
        }
      });
    },
    {
      threshold: 0.1,
      rootMargin: '0px 0px -50px 0px'
    }
  );

  document.querySelectorAll('[data-reveal]').forEach(el => {
    observer.observe(el);
  });
}

// Corresponding CSS
const styles = `
  [data-reveal] {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity 0.6s ease, transform 0.6s ease;
  }

  [data-reveal].visible {
    opacity: 1;
    transform: translateY(0);
  }
`;

Infinite Scroll

// Infinite scroll implementation
class InfiniteScroll {
  constructor(container, loadMore) {
    this.container = container;
    this.loadMore = loadMore;
    this.loading = false;
    this.hasMore = true;

    this.sentinel = document.createElement('div');
    this.sentinel.className = 'infinite-scroll-sentinel';
    this.container.appendChild(this.sentinel);

    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      { rootMargin: '100px' }
    );

    this.observer.observe(this.sentinel);
  }

  async handleIntersection(entries) {
    const entry = entries[0];

    if (entry.isIntersecting && !this.loading && this.hasMore) {
      this.loading = true;
      this.showLoader();

      try {
        const hasMoreData = await this.loadMore();
        this.hasMore = hasMoreData;

        if (!hasMoreData) {
          this.observer.disconnect();
          this.showEndMessage();
        }
      } catch (error) {
        console.error('Error loading more items:', error);
      } finally {
        this.loading = false;
        this.hideLoader();
      }
    }
  }

  showLoader() {
    this.sentinel.innerHTML = '<div class="loader">Loading...</div>';
  }

  hideLoader() {
    this.sentinel.innerHTML = '';
  }

  showEndMessage() {
    this.sentinel.innerHTML = '<p>End of list</p>';
  }
}

// Usage
const feed = document.querySelector('.feed');
let page = 1;

const infiniteScroll = new InfiniteScroll(feed, async () => {
  const response = await fetch(`/api/posts?page=${page}`);
  const data = await response.json();

  data.posts.forEach(post => {
    feed.insertBefore(createPostElement(post), feed.lastChild);
  });

  page++;
  return data.hasMore;
});

Web Workers API

Web Workers allow executing JavaScript in separate threads without blocking the UI.

Heavy Processing in Background

// worker.js - Separate file
self.addEventListener('message', async (event) => {
  const { type, data } = event.data;

  switch (type) {
    case 'PROCESS_DATA':
      const result = processLargeDataset(data);
      self.postMessage({ type: 'PROCESS_COMPLETE', result });
      break;

    case 'CALCULATE':
      const calculation = heavyCalculation(data);
      self.postMessage({ type: 'CALCULATION_COMPLETE', result: calculation });
      break;
  }
});

function processLargeDataset(data) {
  // Processing that would take seconds on main thread
  return data.map(item => {
    return {
      ...item,
      processed: true,
      hash: generateHash(item),
      analytics: calculateAnalytics(item)
    };
  });
}

function heavyCalculation(numbers) {
  // Intensive calculation
  return numbers.reduce((acc, num) => {
    for (let i = 0; i < 1000000; i++) {
      acc += Math.sin(num) * Math.cos(num);
    }
    return acc;
  }, 0);
}
// main.js - Main thread
class WorkerManager {
  constructor(workerPath, poolSize = navigator.hardwareConcurrency || 4) {
    this.workers = [];
    this.queue = [];
    this.activeJobs = new Map();

    for (let i = 0; i < poolSize; i++) {
      const worker = new Worker(workerPath);
      worker.busy = false;

      worker.addEventListener('message', (event) => {
        this.handleWorkerMessage(worker, event);
      });

      this.workers.push(worker);
    }
  }

  handleWorkerMessage(worker, event) {
    const job = this.activeJobs.get(worker);

    if (job) {
      job.resolve(event.data);
      this.activeJobs.delete(worker);
    }

    worker.busy = false;
    this.processQueue();
  }

  async execute(type, data) {
    return new Promise((resolve, reject) => {
      const job = { type, data, resolve, reject };

      const availableWorker = this.workers.find(w => !w.busy);

      if (availableWorker) {
        this.runJob(availableWorker, job);
      } else {
        this.queue.push(job);
      }
    });
  }

  runJob(worker, job) {
    worker.busy = true;
    this.activeJobs.set(worker, job);
    worker.postMessage({ type: job.type, data: job.data });
  }

  processQueue() {
    if (this.queue.length === 0) return;

    const availableWorker = this.workers.find(w => !w.busy);
    if (availableWorker) {
      const job = this.queue.shift();
      this.runJob(availableWorker, job);
    }
  }

  terminate() {
    this.workers.forEach(worker => worker.terminate());
  }
}

// Usage
const workerManager = new WorkerManager('/worker.js');

async function processData() {
  const largeDataset = generateLargeDataset();

  console.time('Worker processing');
  const result = await workerManager.execute('PROCESS_DATA', largeDataset);
  console.timeEnd('Worker processing');

  return result;
}

File System Access API

This API allows reading and writing files on the user's local system.

Text Editor with Local Save

class FileEditor {
  constructor() {
    this.fileHandle = null;
    this.content = '';
    this.unsavedChanges = false;
  }

  async openFile() {
    try {
      const [fileHandle] = await window.showOpenFilePicker({
        types: [
          {
            description: 'Text Files',
            accept: {
              'text/*': ['.txt', '.md', '.json', '.js', '.ts', '.css', '.html']
            }
          }
        ],
        multiple: false
      });

      this.fileHandle = fileHandle;
      const file = await fileHandle.getFile();
      this.content = await file.text();

      return {
        name: file.name,
        content: this.content,
        lastModified: new Date(file.lastModified)
      };
    } catch (error) {
      if (error.name !== 'AbortError') {
        throw error;
      }
    }
  }

  async saveFile() {
    if (!this.fileHandle) {
      return this.saveFileAs();
    }

    try {
      const writable = await this.fileHandle.createWritable();
      await writable.write(this.content);
      await writable.close();

      this.unsavedChanges = false;
      return true;
    } catch (error) {
      console.error('Error saving:', error);
      throw error;
    }
  }

  async saveFileAs() {
    try {
      const fileHandle = await window.showSaveFilePicker({
        types: [
          {
            description: 'Text File',
            accept: { 'text/plain': ['.txt'] }
          },
          {
            description: 'Markdown',
            accept: { 'text/markdown': ['.md'] }
          },
          {
            description: 'JavaScript',
            accept: { 'text/javascript': ['.js'] }
          }
        ]
      });

      this.fileHandle = fileHandle;
      return this.saveFile();
    } catch (error) {
      if (error.name !== 'AbortError') {
        throw error;
      }
    }
  }

  updateContent(newContent) {
    this.content = newContent;
    this.unsavedChanges = true;
  }
}

// Usage
const editor = new FileEditor();

document.getElementById('open-btn').addEventListener('click', async () => {
  const file = await editor.openFile();
  if (file) {
    document.getElementById('editor').value = file.content;
    document.getElementById('filename').textContent = file.name;
  }
});

document.getElementById('save-btn').addEventListener('click', async () => {
  await editor.saveFile();
  showNotification('File saved!');
});

Clipboard API

Modern API for interacting with the clipboard.

Copy to Clipboard with Feedback

class ClipboardManager {
  static async copy(text) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch (error) {
      // Fallback for older browsers
      return this.fallbackCopy(text);
    }
  }

  static fallbackCopy(text) {
    const textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.style.position = 'fixed';
    textarea.style.opacity = '0';
    document.body.appendChild(textarea);
    textarea.select();

    try {
      document.execCommand('copy');
      return true;
    } catch (error) {
      return false;
    } finally {
      document.body.removeChild(textarea);
    }
  }

  static async copyRichContent(html, plainText) {
    try {
      const blob = new Blob([html], { type: 'text/html' });
      const textBlob = new Blob([plainText], { type: 'text/plain' });

      await navigator.clipboard.write([
        new ClipboardItem({
          'text/html': blob,
          'text/plain': textBlob
        })
      ]);

      return true;
    } catch (error) {
      return this.copy(plainText);
    }
  }

  static async paste() {
    try {
      return await navigator.clipboard.readText();
    } catch (error) {
      console.error('Error pasting:', error);
      return null;
    }
  }
}

// Copy button component
function createCopyButton(targetSelector) {
  const button = document.createElement('button');
  button.className = 'copy-btn';
  button.innerHTML = '📋 Copy';

  button.addEventListener('click', async () => {
    const target = document.querySelector(targetSelector);
    const text = target.textContent || target.value;

    const success = await ClipboardManager.copy(text);

    if (success) {
      button.innerHTML = '✅ Copied!';
      button.classList.add('success');

      setTimeout(() => {
        button.innerHTML = '📋 Copy';
        button.classList.remove('success');
      }, 2000);
    }
  });

  return button;
}

Broadcast Channel API

Allows communication between different tabs/windows of the same origin.

Synchronization Between Tabs

// State synchronization between tabs
class TabSync {
  constructor(channelName) {
    this.channel = new BroadcastChannel(channelName);
    this.listeners = new Map();

    this.channel.addEventListener('message', (event) => {
      const { type, payload } = event.data;
      const handler = this.listeners.get(type);

      if (handler) {
        handler(payload);
      }
    });
  }

  send(type, payload) {
    this.channel.postMessage({ type, payload });
  }

  on(type, handler) {
    this.listeners.set(type, handler);
  }

  off(type) {
    this.listeners.delete(type);
  }

  close() {
    this.channel.close();
  }
}

// Usage: Sync logout between tabs
const authSync = new TabSync('auth-channel');

authSync.on('LOGOUT', () => {
  // Another tab logged out
  window.location.href = '/login';
});

authSync.on('USER_UPDATED', (user) => {
  // Update user data in all tabs
  updateUserInterface(user);
});

// When logging out
function logout() {
  clearSession();
  authSync.send('LOGOUT');
  window.location.href = '/login';
}

// Sync theme between tabs
const themeSync = new TabSync('theme-channel');

themeSync.on('THEME_CHANGED', (theme) => {
  document.documentElement.setAttribute('data-theme', theme);
});

function setTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
  themeSync.send('THEME_CHANGED', theme);
}

Resize Observer API

Detects changes in element size efficiently.

Dynamic Responsive Components

// Native container queries with ResizeObserver
class ResponsiveComponent {
  constructor(element, breakpoints = {}) {
    this.element = element;
    this.breakpoints = {
      sm: 480,
      md: 768,
      lg: 1024,
      xl: 1280,
      ...breakpoints
    };

    this.observer = new ResizeObserver(entries => {
      for (const entry of entries) {
        this.updateClasses(entry.contentRect.width);
      }
    });

    this.observer.observe(element);
  }

  updateClasses(width) {
    // Remove all breakpoint classes
    Object.keys(this.breakpoints).forEach(bp => {
      this.element.classList.remove(`container-${bp}`);
    });

    // Add appropriate class
    if (width >= this.breakpoints.xl) {
      this.element.classList.add('container-xl');
    } else if (width >= this.breakpoints.lg) {
      this.element.classList.add('container-lg');
    } else if (width >= this.breakpoints.md) {
      this.element.classList.add('container-md');
    } else if (width >= this.breakpoints.sm) {
      this.element.classList.add('container-sm');
    }
  }

  disconnect() {
    this.observer.disconnect();
  }
}

// Auto-initialize on elements with data-responsive
document.querySelectorAll('[data-responsive]').forEach(el => {
  new ResponsiveComponent(el);
});

Conclusion

Native browser Web APIs have evolved significantly and now offer functionality that previously required external libraries. Mastering these APIs allows you to:

  • Reduce dependencies and bundle size
  • Improve application performance
  • Create richer experiences

Before adding a new dependency, check if the browser already natively offers what you need.

If you want to continue expanding your knowledge in modern JavaScript, I recommend checking out another article: State of JavaScript 2025 where you'll discover ecosystem trends.

Let's go! 🦅

💻 Master JavaScript for Real

The knowledge you gained in this article is just the beginning. There are techniques, patterns, and practices that transform beginner developers into sought-after professionals.

Invest in Your Future

I've prepared complete material for you to master JavaScript:

Payment options:

  • 1x of $4.90 no interest
  • or $4.90 at sight

📖 View Complete Content

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments