Предположим, что у нас есть сайт по продаже билетов, на котором мы хотим в document.title показывать цену, чтобы увеличить удовлетворённость пользователей и дать им знать, что в какой вкладке у них открыто (т.к. мы не знаем, будет ли меняться место отправления/прибытия, дата, любимый перевозчик и так далее, а цена, напротив, для различных комбинаций практически всегда разная). При этом мы знаем, что, начиная с версии 56, Chrome проводит политику блокировки фоновых вкладок. Что это означает для данного функционала?
Согласно документации, rAF (requestAnimationFrame) обычно вызывается 60 раз в секунду. Для экранов, у которых частота обновления выше, это значение будет также выше (хотя вы можете и не замечать это глазом).
Далее написано, что, если вкладка фоновая и не активная, то частота может уменьшиться, что и происходит, как мы увидим ниже.
Проведём эксперимент. В коде под спойлером по requestAnimationFrame простейшим образом обновляется тайтл:
Результат в хроме выглядит следующим образом:
Да, так и должно быть. Давайте посмотрим, что делают другие браузеры:
Результат — только Firefox выполняет rAF в фоновой вкладке. Казалось бы удивительно, что даже IE11 не выполняет rAF в фоне, однако, Per the documentation, Chrome does not call requestAnimationFrame() when a page is in the background. This behavior has been in place since 2011. С 2011, Карл!, что означает, что таким поведением никого не удивить. Не удивить, даже если в бэкграунде играет музыка (через
Поэтому, отвечая на изначальный вопрос «Как показать цену в тайтле», ответ достаточно прост — показывать её не в момент отображения компонента, а в момент получения данных из API. Вторым решением был бы вынос из функции, которая используется для анимации страницы при возврате данных с API (большой лоадер на весь экран красиво убираем просто)), функции-инициализатора. К сожалению, так сложилось, что таких функций почему-то больше чем одна, поэтому этот вариант влечёт за собой большие перестановки и от него решено было отказаться. К слову, setTimeout/setInterval, выполняются корректно, через них возможен третий вариант решения проблемы.
На этом стори можно доделывать и закрывать, а теперь перейдём к тому, что происходит на самом деле, используя исходный код requestAnimationFrame из WebKit:
Первый if довольно понятен, подробнее по suspendScriptedAnimations и setIsVisibleInternal.
Во втором if, значение функции isLowPowerModeEnabled, у каждой ОС будет браться из разных источников; в iOS, вероятно, оно берётся из _didReceiveLowPowerModeChange.
Третий if наиболее интересен; topOrigin это просто SecurityOrigin документа верхнего уровня; сам метод находится здесь, в комментариях в этом методе достаточно подробно расписано, что в нём происходит. Этот метод возвращает true, если два скрипта с разных origin могут общаться между собой (на чтение или на запись). В то же время, у пользователя остаётся место для манёвра при помощи NonInteractedCrossOriginFrame.
Таким образом, webkit-браузеры будут получать блокировку rAF каждый раз, когда они заходят в один из трёх if.
Вернёмся к Firefox. Преобразуем исходную функцию, меняющую заголовок страницы, на следующую:
Как можно заметить, в Firefox/Gecko всё происходит немного иначе, чем в webkit. Если найти реализацию request_animation_frame, то можно заметить, что:
Даже не знаю, кто круче.
Хорошие ссылки:
Согласно документации, rAF (requestAnimationFrame) обычно вызывается 60 раз в секунду. Для экранов, у которых частота обновления выше, это значение будет также выше (хотя вы можете и не замечать это глазом).
Далее написано, что, если вкладка фоновая и не активная, то частота может уменьшиться, что и происходит, как мы увидим ниже.
Проведём эксперимент. В коде под спойлером по requestAnimationFrame простейшим образом обновляется тайтл:
requestAnimationFrame страница
<!DOCTYPE>
<html>
<head>
<script>
requestAnimationFrame(function() {
document.title += ' RAF'
});
</script>
<title>tab</title>
</head>
<body>tab</body>
</html>
Результат в хроме выглядит следующим образом:
Chrome
Да, так и должно быть. Давайте посмотрим, что делают другие браузеры:
IE11
Чтобы не ходить в настройки и не разрешать выполнение скриптов на локальных сайтах, воспользуемся комбинацией команд http-server и lt -p 8080:
* Edge работает аналогично
* Edge работает аналогично
Safari
Firefox
Результат — только Firefox выполняет rAF в фоновой вкладке. Казалось бы удивительно, что даже IE11 не выполняет rAF в фоне, однако, Per the documentation, Chrome does not call requestAnimationFrame() when a page is in the background. This behavior has been in place since 2011. С 2011, Карл!, что означает, что таким поведением никого не удивить. Не удивить, даже если в бэкграунде играет музыка (через
<audio src="sound.mp3" autoplay></audio>
или что бы то ни было ещё), rAF, в фоне, при любых условиях, в любом браузере, отличным от FF, выполняться не будет.Поэтому, отвечая на изначальный вопрос «Как показать цену в тайтле», ответ достаточно прост — показывать её не в момент отображения компонента, а в момент получения данных из API. Вторым решением был бы вынос из функции, которая используется для анимации страницы при возврате данных с API (большой лоадер на весь экран красиво убираем просто)), функции-инициализатора. К сожалению, так сложилось, что таких функций почему-то больше чем одна, поэтому этот вариант влечёт за собой большие перестановки и от него решено было отказаться. К слову, setTimeout/setInterval, выполняются корректно, через них возможен третий вариант решения проблемы.
На этом стори можно доделывать и закрывать, а теперь перейдём к тому, что происходит на самом деле, используя исходный код requestAnimationFrame из WebKit:
int Document::requestAnimationFrame(Ref<RequestAnimationFrameCallback>&& callback)
{
if (!m_scriptedAnimationController) {
#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
m_scriptedAnimationController = ScriptedAnimationController::create(*this, page() ? page()->chrome().displayID() : 0);
#else
m_scriptedAnimationController = ScriptedAnimationController::create(*this, 0);
#endif
// It's possible that the Page may have suspended scripted animations before
// we were created. We need to make sure that we don't start up the animation
// controller on a background tab, for example.
if (!page() || page()->scriptedAnimationsSuspended())
m_scriptedAnimationController->suspend();
if (page() && page()->isLowPowerModeEnabled())
m_scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::LowPowerMode);
if (!topOrigin().canAccess(securityOrigin()) && !hasHadUserInteraction())
m_scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame);
}
return m_scriptedAnimationController->registerCallback(WTFMove(callback));
}
Первый if довольно понятен, подробнее по suspendScriptedAnimations и setIsVisibleInternal.
Во втором if, значение функции isLowPowerModeEnabled, у каждой ОС будет браться из разных источников; в iOS, вероятно, оно берётся из _didReceiveLowPowerModeChange.
Третий if наиболее интересен; topOrigin это просто SecurityOrigin документа верхнего уровня; сам метод находится здесь, в комментариях в этом методе достаточно подробно расписано, что в нём происходит. Этот метод возвращает true, если два скрипта с разных origin могут общаться между собой (на чтение или на запись). В то же время, у пользователя остаётся место для манёвра при помощи NonInteractedCrossOriginFrame.
Таким образом, webkit-браузеры будут получать блокировку rAF каждый раз, когда они заходят в один из трёх if.
Вернёмся к Firefox. Преобразуем исходную функцию, меняющую заголовок страницы, на следующую:
Меняем тайтл на каждый animationFrame
requestAnimationFrame(function handler() {
document.title += 'R'
requestAnimationFrame(handler);
});
Результат
Как можно заметить, в Firefox/Gecko всё происходит немного иначе, чем в webkit. Если найти реализацию request_animation_frame, то можно заметить, что:
- // TODO: Should tick animation only when document is visible — что означает, что когда-нибудь поведение будет соответствовать поведению остальных браузеров, поэтому на том, что происходит сейчас, можно особо не зацикливаться.
- Существует is_faking_animation_frames где оказывается, что ребята из Gecko, в случае, если у вас много фейковых анимаций, выбрасывают вас на FakeRequestAnimationFrameCallback.
Даже не знаю, кто круче.
Хорошие ссылки:
Поделиться с друзьями
ertaquo
Простите, конечно, но на кой черт менять document.title в requestAnimationFrame? Оно вообще не для этого предназначено.
Насколько мне известно, на текущий момент для подобного нужно использовать Service Workers, если они доступны.
samsdemon
Никто и не использует, это глупо. Была проблема в том, что у нас новая владка открывается в новом окне, и, если пользователь переходит назад откуда он пришёл, тайтл в новой вкладке не обновляется.
По факту это было сделано даже не в Service Workers, а просто за пределами requestAnimationFrame, который у нас используется для перехода из состояния «загружается» в состояние «готово».
bano-notit
Может я чего-то не допонимаю конечно… Вы его использовали вместо DOMContentLoaded или вместо setImmediate?
samsdemon
Смотрите. У нас есть страница с заказом места в самолёте, например. Вы переходите на неё со страницы с результатами поиска полётов, выбрав один из них. На этой странице 100500 компонентов, которые обновляются через requestAnimationFrame, чтобы было красиво, а не всем скопом пользователя завалить.
Aingis
Всё это было
в Симпсонахна MoscowJS: