Back

Como Detectar Quando uma Aba do Navegador se Torna Inativa

Como Detectar Quando uma Aba do Navegador se Torna Inativa

Você tem um intervalo de polling disparando a cada dez segundos, um vídeo reproduzindo em segundo plano ou um loop de animação em execução — tudo consumindo recursos para um usuário que trocou de aba há cinco minutos. A correção é direta quando você conhece a API certa para usar.

Principais Conclusões

  • A Page Visibility API é a forma moderna e confiável de detectar mudanças de visibilidade de abas, substituindo as antigas abordagens com window.onfocus e window.onblur.
  • Use document.hidden ou document.visibilityState em conjunto com o evento visibilitychange para reagir quando uma aba se tornar inativa ou ativa.
  • Aplicações práticas incluem pausar polling, interromper reprodução de mídia, salvar automaticamente rascunhos de formulários e enviar analytics de fim de sessão.
  • Combine visibilitychange com pagehide e navigator.sendBeacon() para rastreamento confiável de fim de sessão, e evite o evento unload porque ele quebra o cache de avançar/voltar (back/forward cache).
  • Sempre remova listeners durante o desmonte de componentes em aplicações single-page para evitar vazamentos de memória.

A Ferramenta Certa: A Page Visibility API

Soluções antigas dependiam dos eventos de focus e blur da windowwindow.onfocus e window.onblur. Esses eventos detectam se a janela do navegador está com o foco do teclado, não se a aba está realmente visível. Em configurações de tela dividida ou fluxos de trabalho com múltiplas janelas, uma aba pode estar totalmente visível sem ter o foco, e vice-versa. Eles também se comportavam de forma inconsistente entre navegadores especificamente para a troca de abas.

A Page Visibility API resolve isso adequadamente. Ela informa exatamente se a página está visível para o usuário, independentemente do estado de foco.

Duas propriedades fornecem o estado atual:

  • document.visibilityState — retorna "visible" ou "hidden"
  • document.hidden — retorna true quando a aba está oculta, false quando visível

Não são necessários prefixos de fornecedor em navegadores modernos. A API tem amplo suporte em Chrome, Firefox, Safari e Edge — tanto desktop quanto mobile.

Escutando o Evento visibilitychange

O evento visibilitychange é disparado em document sempre que a aba se torna oculta ou visível novamente. Aqui está o padrão básico:

document.addEventListener("visibilitychange", () => {
  if (document.hidden) {
    console.log("Tab is hidden — pause or reduce work");
  } else {
    console.log("Tab is visible — resume work");
  }
});

Você também pode ler document.visibilityState diretamente se precisar do valor em string em vez de um booleano.

Casos de Uso Práticos

Pausando e Retomando Polling

Em vez de fazer requisições à API enquanto o usuário está em outra aba, pause o intervalo e retome quando ele retornar:

let intervalId = null;

function startPolling() {
  if (intervalId !== null) return;
  intervalId = setInterval(fetchData, 10000);
}

function stopPolling() {
  clearInterval(intervalId);
  intervalId = null;
}

document.addEventListener("visibilitychange", () => {
  document.hidden ? stopPolling() : startPolling();
});

startPolling();

Observação: navegadores aplicam throttling em setInterval em abas em segundo plano em vez de pará-lo completamente, então depender de precisão temporal em abas ocultas é, de qualquer forma, não confiável. Pausar explicitamente é mais limpo. Proteger-se contra intervalos duplicados também evita que múltiplos timers se empilhem caso startPolling seja chamado duas vezes.

Pausando Reprodução de Mídia

const video = document.querySelector("video");

document.addEventListener("visibilitychange", () => {
  if (document.hidden) {
    video.pause();
  } else {
    video.play().catch(() => {
      // Autoplay may be blocked by the browser; handle silently
    });
  }
});

O handler .catch() é importante porque video.play() retorna uma promise que pode ser rejeitada se o navegador bloquear o autoplay, o que de outra forma lançaria um erro não tratado.

Salvando Automaticamente um Rascunho de Formulário

Dispare um salvamento quando o usuário sair da aba, em vez de em um timer fixo:

document.addEventListener("visibilitychange", () => {
  if (document.hidden) {
    saveDraft();
  }
});

Enviando Analytics Antes do Usuário Sair

Para dados de fim de sessão, combine o evento visibilitychange com pagehide e use navigator.sendBeacon() para enviar dados de forma confiável sem bloquear a navegação:

document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden") {
    navigator.sendBeacon(
      "/analytics",
      JSON.stringify({ event: "tab_hidden" })
    );
  }
});

window.addEventListener("pagehide", () => {
  navigator.sendBeacon(
    "/analytics",
    JSON.stringify({ event: "page_unload" })
  );
});

Evite o evento unload para esse propósito — ele é não confiável em navegadores modernos e quebra o back/forward cache (bfcache), o que degrada o desempenho de navegação. Em navegadores móveis, visibilitychange com estado "hidden" é frequentemente o único sinal confiável de que uma sessão terminou, já que pagehide e beforeunload nem sempre são disparados.

Limpando Event Listeners

Sempre remova listeners quando eles não forem mais necessários, particularmente em aplicações single-page:

function handleVisibility() {
  console.log(document.visibilityState);
}

document.addEventListener("visibilitychange", handleVisibility);

// Later, when tearing down:
document.removeEventListener("visibilitychange", handleVisibility);

Lembre-se de que a referência passada para removeEventListener deve ser exatamente a mesma referência de função usada em addEventListener. Arrow functions inline não podem ser removidas porque cada uma cria uma nova referência.

Conclusão

A Page Visibility API — especificamente document.hidden, document.visibilityState e o evento visibilitychange — é a maneira correta e moderna de detectar quando uma aba do navegador se torna inativa. Ela funciona de forma confiável em todos os navegadores atuais, lida com a troca de abas com precisão onde os eventos focus e blur de window falham, e combina bem com navigator.sendBeacon() e o evento pagehide para tratamento de fim de sessão. Use-a para interromper trabalho desnecessário, salvar estado ou rastrear engajamento, e limpe seus listeners quando terminar.

Perguntas Frequentes

Ambos informam se a página está visível, mas retornam tipos diferentes. document.hidden retorna um booleano, o que é conveniente para verificações condicionais rápidas. document.visibilityState retorna uma string, atualmente visible ou hidden, que é mais descritiva e à prova de futuro caso estados adicionais sejam adicionados posteriormente. Use o que melhor se adequa ao seu estilo de código.

Os navegadores aplicam throttling em timers em segundo plano em vez de pará-los, tipicamente limitando-os a uma vez por segundo ou mais lento. O intervalo ainda executa, mas em taxas imprevisíveis, então lógica sensível a tempo torna-se não confiável. A solução mais limpa é chamar clearInterval dentro do seu handler de visibilitychange e reiniciar o intervalo quando a aba se tornar visível novamente.

Prefira visibilitychange combinado com pagehide e navigator.sendBeacon. Os eventos beforeunload e unload são não confiáveis em mobile e quebram o back/forward cache, prejudicando o desempenho. O evento visibilitychange com estado hidden é o sinal mais consistente entre navegadores desktop e mobile para detectar que um usuário saiu da página.

Sim, mas o comportamento depende do pai. Um iframe geralmente herda o estado de visibilidade de seu documento pai. Ocultar o próprio iframe com CSS, como `display: none`, não dispara eventos `visibilitychange` dentro do iframe. Cada iframe tem seu próprio objeto document, então você escuta visibilitychange no document do iframe, não na janela superior.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay