![Кот Шрёдингера](https://habrastorage.org/getpro/habr/post_images/597/1ad/fb4/5971adfb4b87c628becfafc5199970b1.jpg)
Page Visibility API — новое API в JavaScript, которое позволяет узнать, видит ли пользователь ваш сайт или же он, например, открыл другой таб.
Каким образом это API может сделать наш Веб дружелюбнее и уютнее? Ну самое очевидное:
- Сделать сайт более дружелюбным к пользователю, «поднять юзабилити». Например, отключать слайдшоу или ставить видео на паузу, когда вы переключаетесь в другой таб (например, вы смотрите видео на YouTube и вам приходит срочное эл. письмо).
- Не потреблять лишних ресурсов. Выключать лишнюю логику, когда она не нужна, так как пользователь не видит сайт. Например, в фоновом табе отключать сложные JS-рассчёты или реже проверять новые сообщения по AJAX.
- Считать более точную статистику. Например, не засчитывать пользователей, которые открыли ваш сайт в новом табе и закрыли его не просматривая.
- Поддерживать новую технологию пререндеринга из Google Chrome, когда браузеру заранее загружает и рендерит указанную страницу, чтобы открыть её мгновенно. Например, в поиске Google первый результат выдачи будет отмечен на прередеринг.
- Сделать эмулятор кота Шрёдингера (на иллюстрации), который отобразит живого или мёртвого кота только тогда, когда пользователь откроет загруженный в фоне таб.
Чтобы сделать работу с Page Visibility API более удобной, я (во славу Злых марсиан) разработал библиотеку Visibility.js. Она позволяет забыть о вендорных префиксах и добавляет «сахара» высокоуровневых функций, чтобы писать короткий чистый код (например,
Visibility.every
— аналог setInterval
, но работает только, если сайт в открытом табе).Милый пример видео-проигрывателя, который останавливает видео, когда страница становится невидимой (открывать в Google Chrome 13).
Поддержка в браузерах
Page Visibility API уже сейчас поддерживается в Google Chrome 13 и IE 10. Для Firefox 5 и выше есть хак MozVisibility от private_face, который эмулирует Page Visibilty API (этот хак надо подключать в странице перед Visibility.js).
Уже есть черновик стандарта Page Visibility API от W3C, так что поддержка в других браузерах — вопрос времени.
Но совсем не обязательно, чтобы браузеры всех ваших пользователей поддерживали этот API — это просто улучшение, а не добавление нового функционала, как тег
<video>
. Если поддержка есть — пользователю будет удобнее, если нет — сайт будет работать как обычные сайты и думать, что пользователь всегда видит сайт. Высокоуровневые функции в Visibility.js специально так сделаны, чтобы разработчик мог не задумываться, есть ли поддержка API или нет.Состояния
Сейчас в стандарте есть 4 возможных состояния видимости страницы:
visible
— пользователь сейчас видит страницу.hidden
— страница не видна для пользователя, так как в браузере открыт другой таб, окно браузера свёрнуто или ОС вообще заблокировало экран. Правда в реальности Chrome проверяет только открыт ли текущий таб или нет, сворачивание браузера никак не влияет.prerender
— браузер загрузил и отрендерил страницу заранее, чтобы потом мгновенно показать пользователю. То есть, сейчас пользователь страницу не видит, лишние вычисления и мультимедиа надо убрать, а в статистике такой просмотр пока не считать. Вся технология поддерживается только в Google Chrome, хотя уже есть в стандарте.preview
— сайт открыт в маленьком окне предпросмотра. Например, в мозайке часто открываемых сайтов в новом табе. Теоретическое свойство из стандарта, так как пока не поддерживается ни одним браузером.
А что будет, когда в стандарт добавят ещё одно состояние, а вам нужно будет проверить просто видим ли сайт для пользователя или нет. Для этого есть свойство
document.hidden
(не забывайте про вендорные префиксы, в Chrome оно будет document.webkitHidden
) или метод Visibility.hidden()
в Visibility.js. Если вам нужно проверить, видим ли сайт — используйте именно это свойство, а не сравнивайте название состояния с "hidden"
.Visibility.js
Чтобы не пугать низкоуровневым кодом с кучей проверок на вендорные префиксы, я буду показывать работу с Page Visibility API сразу на примере Visibility.js, а в конце статьи расскажу и об низкоуровневых методов из стандарта API.
Код библиотеки полностью покрыт модульными тестами, отдокументирован и библиотека уже успешно используется на русском Групоне. В сжатой версии она весит всего 1 КБ, зато избавляет вас от кучи лишнего кода.
Visibility.every
Visibility.every(interval, callback)
— это аналог setInterval(callback, interval)
, но запускает callback
, только если пользователь сейчас видит страницу.Например, будем показывать анимацию обратного отсчёта, только когда пользователь видит страницу:
Visibility.every(1000, function() {
updateCountdownAnimation();
});
Visibility.every(visible, hidden, callback)
может принимать 2 интервала времени — visible
будет использовать, когда страница видима для пользователя, а hidden
— когда скрыта.Например, можно проверять новые сообщения каждую минуту, когда пользователь открыл сайт (то есть ему это важно). А когда пользователь не видит сайт (читает другую страницу) будем экономить его трафик и проверять почту каждые 5 минут:
var minute = 60 * 1000;
Visibility.every(minute, 5 * minute, function () {
checkForEmail();
});
Но вообще указывать время в милисекундах — издевательство над человеком. Поэтому Visibility.js поддерживает интеграцию с jQuery Chrono plugin (его нужно просто подключить перед Visibility.js). Код становится понятным и мило засахаренным:
Visibility.every('minute', '5 minutes', function () {
checkNewMails();
});
Чтобы остановить таймер, запущенный с помощью
Visibility.every
, нужно использовать Visibility.stop(timerID)
(clearInterval
работать не будет):var slideshow = Visibility.every(5 * 1000, function () {
nextSlide();
});
$('.stopSlideshow').click(function () {
Visibility.stop(slideshow);
});
Если браузер не поддерживает Page Visibility API, то
Visibility.every
будет считать, что пользователь всегда видит сайт (то есть она становится полным аналогом setInterval
, но ничего страшного не происходит).Visibility.onVisible
Другая стандартная ситуация — когда мы ждём, пока пользователь не увидит сайт (например, потому что открыл ссылку в фоновом табе). Для этого есть метод
Visibility.onVisible(callback)
, который выполняет код только тогда, когда страница становится видимой. Если страница уже видима, то callback
вызовется сразу же.Например, при посещении сайты мы показываем какое-то уведомление и через 10 секунд красиво скрываем его. Но если пользователь откроет сайт сразу в фоновом табе, то он может пропустить уведомление. Давайте тогда отсчитывать 10 секунд после того, как пользователь действительно увидит сайт:
Visibility.onVisible(function () {
setTimeout(function() {
Notification.hide();
}, 10 * 1000);
});
Если браузер не поддерживает Page Visibility API, то
Visibility.onVisible(callback)
вызовет callback
сразу же.Visibility.afterPrerendering
У Firefox есть
rel="prefetch"
для ссылок, который говорит браузеру, что пользователь скорее всего откроет потом эту ссылку, так что браузер заранее загружает её содержимое. Это нужно, например, чтобы загрузить следующую главу статьи или для первого результата в поисковой выдаче.Google Chrome пошёл дальше и сделал
rel="prerender"
— он не только загружает, но и рендерит страницу заранее, чтобы открыть её мгновенно (видео примера от Google).Однако есть множество ограничений, когда страница не будет пререндериться. Запрещается делать AJAX-запросы, размещать аудио или видео, открывать попапы, проводить тяжёлые вычисления. Плюс желательно не учитывать пользователя в статистике посещений, пока он действительно не откроет сайт.
Для всех этих задач есть
Visibility.afterPrerendering(callback)
, который выполнит callback
, только когда страница откроется по настоящему (то есть выйдет из состояния пререндеринга) или выполнит callback
сразу же, если страница сразу была нормально открыта. В callback
можно включить автообновление через AJAX, добавить на страницу <video>
и посчитать пользователя в статистике.Visibility.afterPrerendering(function () {
Statistics.countVisitor();
});
Если браузер не поддерживает Page Visibility API или пререндеринг, то
Visibility.afterPrerendering(callback)
вызовет callback
сразу же.Низкоуровневые функции
Если вы хотите понять, на основе чего работает весь «сахар» из примеров выше, или вы сделать что-то более сложное, то вам понадобятся низкоуровневые функции Visibility.js. Тут же я покажу, как Page Visibility API работает.
Visibility.isSupported()
возвращает true
, если браузер поддерживает Page Visibility API. Браузер не поддерживающий Page Visibility API можно легко узнать — у него document.hidden
будет undefined
, а не true
или false
(только надо не забывать про вендорный префикс, например, document.webkitHidden
).Visibility.state()
возвращает имя состояния ("visible"
, "hidden"
или "prerenderer"
). Этот метод просто смотрит в свойство document.visibilityState
с учётом вендорного префикса (например, document.webkitVisibilityState
). Небольшой пример для закрепления:if( Visibility.isSupported() ) {
if ( 'hidden' == Visibility.state() ) {
Statistics.userOpenPageInBackgroundTab();
}
if ( 'prerender' == Visibility.state() ) {
Statistics.pageIsPrerendering();
}
})
Если нужно просто проверить, видит ли пользователь страницу или нет, то лучше использовать
Visibility.hidden()
(так как список состояний может в дальнейшем пополниться). Она просто смотрит в свойство document.hidden
. Следующий пример включает автопроигрывание видео, только если страница открылась сразу в активном табе (а не в новом фоновом):$(document).load(function () {
if ( !Visibility.hidden() ) {
VideoPlayer.play();
}
});
Чтобы следить за изменением состояния страницы, у
document
есть событие visibilitychange
(в Chrome — webkitvisibilitychange
). В Visibility.js есть более короткий способ — метод Visibility.change(callback)
сам вешает обработчик события и вызывает callback
при каждой смене видимости страницы. Первый аргумент у callback
будет объект события, а второй — имя состояния. Пример:Visibility.change(function (e, state) {
Statistics.trackChangeVisibility(state);
});
Установка
- Если вы используете Ruby on Rails 3.1, то вам проще всего. Подключите
visibilityjs
гем в Gemfile:
gem 'visibilityjs'
и подключите библиотеку вapp/assets/javascripts/application.js.coffee
:
#= require visibility
- Если вы используете какой-то сборщик статики, например, Jammit, то скачайте lib/visibility.js в
public/javascripts/lib
вашего проекта и подключите Visibility.js в настройках пакетов вconfig/assets.yml
:
javascripts: application: - public/javascripts/lib/visibility.js
- Если вы почему-то не думаете о клиентской оптимизации и не объединяете все JS-файлы проекта для последующего сжатия, то можете скачать уже сжатую версию Visibility.js — visibility.min.js.