Как определить, что вкладка браузера стала неактивной
У вас есть интервал опроса, срабатывающий каждые десять секунд, видео, проигрывающееся в фоне, или цикл анимации — и всё это потребляет ресурсы для пользователя, который переключился на другую вкладку пять минут назад. Решение очевидно, как только вы узнаете правильный API.
Ключевые выводы
- Page Visibility API — это современный и надёжный способ определять изменения видимости вкладки, заменяющий устаревшие подходы с
window.onfocusиwindow.onblur. - Используйте
document.hiddenилиdocument.visibilityStateвместе с событиемvisibilitychange, чтобы реагировать на переход вкладки в неактивное или активное состояние. - Практические применения включают приостановку опросов, остановку воспроизведения медиа, автосохранение черновиков форм и отправку аналитики при завершении сессии.
- Сочетайте
visibilitychangeсpagehideиnavigator.sendBeacon()для надёжного отслеживания завершения сессии и избегайте событияunload, поскольку оно ломает back/forward cache. - Всегда удаляйте слушатели при размонтировании компонентов в одностраничных приложениях, чтобы предотвратить утечки памяти.
Правильный инструмент: Page Visibility API
Старые решения опирались на события focus и blur объекта window — window.onfocus и window.onblur. Они определяют, есть ли у окна браузера фокус клавиатуры, а не действительно ли вкладка видима. В режиме разделённого экрана или при работе с несколькими окнами вкладка может быть полностью видимой, не имея при этом фокуса, и наоборот. Кроме того, их поведение при переключении вкладок было непоследовательным в разных браузерах.
Page Visibility API решает эту задачу корректно. Он точно сообщает, видима ли страница пользователю, независимо от состояния фокуса.
Текущее состояние можно узнать с помощью двух свойств:
document.visibilityState— возвращает"visible"или"hidden"document.hidden— возвращаетtrue, когда вкладка скрыта, иfalse, когда видима
В современных браузерах вендорные префиксы не нужны. API имеет широкую поддержку в Chrome, Firefox, Safari и Edge — как на десктопе, так и на мобильных устройствах.
Прослушивание события visibilitychange
Событие visibilitychange срабатывает на document всякий раз, когда вкладка становится скрытой или снова видимой. Базовый паттерн выглядит так:
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
console.log("Tab is hidden — pause or reduce work");
} else {
console.log("Tab is visible — resume work");
}
});
Вы также можете напрямую читать document.visibilityState, если вам нужно строковое значение, а не булево.
Практические сценарии использования
Приостановка и возобновление опроса
Вместо того чтобы отправлять API-запросы, пока пользователь находится на другой вкладке, приостановите интервал и возобновите его при возвращении:
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();
Обратите внимание: браузеры тротлят setInterval в фоновых вкладках, а не полностью останавливают его, поэтому полагаться на точность таймингов в скрытых вкладках в любом случае ненадёжно. Явная приостановка чище. Защита от дублирующих интервалов также предотвращает наложение нескольких таймеров, если startPolling будет случайно вызван дважды.
Приостановка воспроизведения медиа
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
});
}
});
Обработчик .catch() важен, поскольку video.play() возвращает промис, который может быть отклонён, если браузер блокирует автовоспроизведение — иначе это привело бы к необработанной ошибке.
Автосохранение черновика формы
Запускайте сохранение, когда пользователь покидает вкладку, а не по фиксированному таймеру:
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
saveDraft();
}
});
Discover how at OpenReplay.com.
Отправка аналитики перед уходом пользователя
Для данных о завершении сессии комбинируйте событие visibilitychange с pagehide и используйте navigator.sendBeacon() для надёжной отправки данных без блокировки навигации:
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" })
);
});
Избегайте использования события unload для этой цели — оно ненадёжно в современных браузерах и ломает back/forward cache (bfcache), что снижает производительность навигации. В мобильных браузерах visibilitychange со состоянием "hidden" часто является единственным надёжным сигналом завершения сессии, так как pagehide и beforeunload срабатывают не всегда.
Очистка слушателей событий
Всегда удаляйте слушатели, когда они больше не нужны, особенно в одностраничных приложениях:
function handleVisibility() {
console.log(document.visibilityState);
}
document.addEventListener("visibilitychange", handleVisibility);
// Later, when tearing down:
document.removeEventListener("visibilitychange", handleVisibility);
Помните, что ссылка, передаваемая в removeEventListener, должна быть точно той же ссылкой на функцию, что использовалась в addEventListener. Встроенные стрелочные функции удалить нельзя, поскольку каждая создаёт новую ссылку.
Заключение
Page Visibility API — а именно document.hidden, document.visibilityState и событие visibilitychange — это корректный и современный способ определять, когда вкладка браузера становится неактивной. Он надёжно работает во всех актуальных браузерах, точно обрабатывает переключение вкладок там, где события focus и blur объекта window оказываются недостаточны, и хорошо сочетается с navigator.sendBeacon() и событием pagehide для обработки завершения сессии. Используйте его, чтобы остановить ненужную работу, сохранить состояние или отслеживать вовлечённость, и не забывайте очищать слушатели по завершении.
Часто задаваемые вопросы
Оба сообщают, видима ли страница, но возвращают разные типы. document.hidden возвращает булево значение, что удобно для быстрых условных проверок. document.visibilityState возвращает строку — в настоящее время либо visible, либо hidden — что более описательно и устойчиво к будущим изменениям, если будут добавлены дополнительные состояния. Используйте то, что лучше вписывается в стиль вашего кода.
Браузеры тротлят фоновые таймеры, а не останавливают их, обычно ограничивая частоту срабатывания до одного раза в секунду или реже. Интервал продолжает работать, но с непредсказуемой частотой, поэтому логика, чувствительная к таймингам, становится ненадёжной. Самое чистое решение — вызвать clearInterval внутри обработчика visibilitychange и перезапустить интервал, когда вкладка снова станет видимой.
Предпочтительнее visibilitychange в сочетании с pagehide и navigator.sendBeacon. События beforeunload и unload ненадёжны на мобильных устройствах и ломают back/forward cache, ухудшая производительность. Событие visibilitychange со состоянием hidden — наиболее последовательный сигнал на десктопных и мобильных браузерах для определения того, что пользователь покинул страницу.
Да, но поведение зависит от родителя. Iframe, как правило, наследует состояние видимости родительского документа. Скрытие самого iframe через CSS, например `display: none`, не вызывает событий `visibilitychange` внутри iframe. У каждого iframe свой объект document, поэтому слушать visibilitychange нужно на документе iframe, а не на верхнем окне.
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.