Voltar para o Blog

Web APIs Modernas do Navegador Que Todo Desenvolvedor JavaScript Deveria Conhecer

Olá HaWkers, os navegadores modernos oferecem um arsenal impressionante de APIs nativas que muitos desenvolvedores desconhecem. Essas APIs podem substituir bibliotecas pesadas e adicionar funcionalidades avançadas às suas aplicações.

Você sabia que é possível detectar quando elementos entram na viewport, executar código em threads separadas, ou acessar o sistema de arquivos local, tudo com JavaScript nativo?

Por Que Usar APIs Nativas do Navegador

Antes de instalar mais uma dependência, considere o que o navegador já oferece gratuitamente.

Vantagens das APIs Nativas

Performance:

  • Zero download adicional para o usuário
  • Implementações otimizadas em código nativo
  • Menor bundle size

Manutenção:

  • Sem dependências para atualizar
  • APIs padronizadas pelo W3C
  • Documentação oficial no MDN

Compatibilidade:

  • Maioria das APIs modernas tem suporte em 95%+ dos browsers
  • Polyfills disponíveis quando necessário

Intersection Observer API

Esta API permite detectar quando elementos entram ou saem da viewport de forma extremamente eficiente.

Lazy Loading de Imagens

// Implementação nativa de lazy loading
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();
  }
}

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

Animações ao Scroll (Scroll Reveal)

// Revelação de elementos no scroll
function initScrollReveal() {
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.classList.add('visible');

          // Atraso baseado no 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);
  });
}

// CSS correspondente
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

// Implementação de scroll infinito
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('Erro ao carregar mais itens:', error);
      } finally {
        this.loading = false;
        this.hideLoader();
      }
    }
  }

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

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

  showEndMessage() {
    this.sentinel.innerHTML = '<p>Fim da lista</p>';
  }
}

// Uso
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 permitem executar JavaScript em threads separadas, sem bloquear a UI.

Processamento Pesado em Background

// worker.js - Arquivo separado
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) {
  // Processamento que demoraria segundos na thread principal
  return data.map(item => {
    return {
      ...item,
      processed: true,
      hash: generateHash(item),
      analytics: calculateAnalytics(item)
    };
  });
}

function heavyCalculation(numbers) {
  // Cálculo intensivo
  return numbers.reduce((acc, num) => {
    for (let i = 0; i < 1000000; i++) {
      acc += Math.sin(num) * Math.cos(num);
    }
    return acc;
  }, 0);
}
// main.js - Thread principal
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());
  }
}

// Uso
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

Esta API permite ler e escrever arquivos no sistema local do usuário.

Editor de Texto com Salvamento Local

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

  async openFile() {
    try {
      const [fileHandle] = await window.showOpenFilePicker({
        types: [
          {
            description: 'Arquivos de Texto',
            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('Erro ao salvar:', error);
      throw error;
    }
  }

  async saveFileAs() {
    try {
      const fileHandle = await window.showSaveFilePicker({
        types: [
          {
            description: 'Arquivo de Texto',
            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;
  }
}

// Uso
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('Arquivo salvo!');
});

Clipboard API

API moderna para interagir com a área de transferência.

Copy to Clipboard com Feedback

class ClipboardManager {
  static async copy(text) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch (error) {
      // Fallback para browsers antigos
      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('Erro ao colar:', error);
      return null;
    }
  }
}

// Componente de botão de cópia
function createCopyButton(targetSelector) {
  const button = document.createElement('button');
  button.className = 'copy-btn';
  button.innerHTML = '📋 Copiar';

  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 = '✅ Copiado!';
      button.classList.add('success');

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

  return button;
}

Broadcast Channel API

Permite comunicação entre diferentes abas/janelas do mesmo origem.

Sincronização Entre Abas

// Sincronização de estado entre abas
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();
  }
}

// Uso: Sincronizar logout entre abas
const authSync = new TabSync('auth-channel');

authSync.on('LOGOUT', () => {
  // Outra aba fez logout
  window.location.href = '/login';
});

authSync.on('USER_UPDATED', (user) => {
  // Atualiza dados do usuário em todas as abas
  updateUserInterface(user);
});

// Quando fazer logout
function logout() {
  clearSession();
  authSync.send('LOGOUT');
  window.location.href = '/login';
}

// Sincronizar tema entre abas
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

Detecta mudanças no tamanho de elementos de forma eficiente.

Componentes Responsivos Dinâmicos

// Container queries nativas com 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 todas as classes de breakpoint
    Object.keys(this.breakpoints).forEach(bp => {
      this.element.classList.remove(`container-${bp}`);
    });

    // Adiciona classe apropriada
    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-inicializar em elementos com data-responsive
document.querySelectorAll('[data-responsive]').forEach(el => {
  new ResponsiveComponent(el);
});

Conclusão

As Web APIs nativas do navegador evoluíram significativamente e agora oferecem funcionalidades que antes exigiam bibliotecas externas. Dominar essas APIs permite:

  • Reduzir dependências e bundle size
  • Melhorar performance da aplicação
  • Criar experiências mais ricas

Antes de adicionar uma nova dependência, verifique se o navegador já não oferece o que você precisa nativamente.

Se você quer continuar expandindo seus conhecimentos em JavaScript moderno, recomendo que dê uma olhada em outro artigo: State of JavaScript 2025 onde você vai descobrir as tendências do ecossistema.

Bora pra cima! 🦅

💻 Domine JavaScript de Verdade

O conhecimento que você adquiriu neste artigo é só o começo. Há técnicas, padrões e práticas que transformam desenvolvedores iniciantes em profissionais requisitados.

Invista no Seu Futuro

Preparei um material completo para você dominar JavaScript:

Formas de pagamento:

  • 1x de R$9,90 sem juros
  • ou R$9,90 à vista

📖 Ver Conteúdo Completo

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário