В Chrome 88 (январь 2021 г.) в определенных условиях будут сильно регулироваться связанные таймеры JavaScript для скрытых страниц. Это уменьшит загрузку процессора, что также уменьшит расход заряда батареи. В некоторых крайних случаях это может изменить поведение, но таймеры часто используются там, где другой API будет более эффективным и надежным.
Хорошо, это был довольно тяжелый жаргонизм и немного двусмысленный. Давайте покопаемся…
Терминология
Скрытые страницы
Как правило, «скрытая» означает, что активна другая вкладка или окно свернуто, но браузеры могут считать страницу скрытой, когда ее содержимое полностью не видно. Некоторые браузеры идут дальше других, но вы всегда можете использовать API видимости страницы , чтобы отслеживать, когда браузер считает, что видимость изменилась.
Таймеры JavaScript
Под таймерами JavaScript я подразумеваю setTimeout
и setInterval
, которые позволяют вам запланировать обратный вызов когда-нибудь в будущем. Таймеры полезны, и они никуда не денутся, но иногда их используют для опроса состояния, когда событие будет более эффективным и точным.
Связанные таймеры
Если вы вызываете setTimeout
в той же задаче, что и обратный вызов setTimeout
, второй вызов будет «связанным». При использовании setInterval
каждая итерация является частью цепочки . Это может быть проще понять с помощью кода:
let chainCount = 0;
setInterval(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
}, 500);
И:
let chainCount = 0;
function setTimeoutChain() {
setTimeout(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
setTimeoutChain();
}, 500);
}
Как работает дросселирование
Регулирование происходит поэтапно:
Минимальное регулирование
Это происходит с таймерами, которые запланированы, когда выполняется любое из следующих условий:
- Страница видна .
- Страница издавала шумы в течение последних 30 секунд. Это может быть любой звуковой API, но тихая звуковая дорожка не учитывается.
Таймер не регулируется, если запрошенный тайм-аут не меньше 4 мс, а количество цепочек не равно 5 или больше, и в этом случае тайм-аут устанавливается равным 4 мс. Это не ново; браузеры делали это уже много лет.
Регулирование
Это происходит с таймерами, которые запланированы, когда минимальное регулирование не применяется, и выполняется любое из следующих условий:
- Количество цепочек меньше 5.
- Страница была скрыта менее 5 минут.
- WebRTC используется. В частности, существует
RTCPeerConnection
с «открытым»RTCDataChannel
или «живым»MediaStreamTrack
.
Браузер будет проверять таймеры в этой группе раз в секунду . Поскольку они проверяются только один раз в секунду, таймеры с одинаковым временем ожидания объединяются вместе, объединяя время, необходимое вкладке для запуска кода. Это тоже не новость; браузеры делают это в некоторой степени уже много лет.
Интенсивное регулирование
Хорошо, вот что нового в Chrome 88. Интенсивное регулирование происходит с таймерами, которые запланированы, когда не применяется ни одно из минимальных условий регулирования или условий регулирования , и выполняются все следующие условия:
- Страница была скрыта более 5 минут.
- Количество цепей – 5 или больше.
- Страница молчала не менее 30 секунд.
- WebRTC не используется.
В этом случае браузер будет проверять таймеры в этой группе раз в минуту . Как и раньше, это означает, что таймеры будут выполнять эти ежеминутные проверки вместе.
Обходные пути
Обычно существует лучшая альтернатива таймеру, или таймеры можно комбинировать с чем-то еще, чтобы щадить процессоры и время автономной работы.
Государственный опрос
Это наиболее распространенное (неправильное) использование таймеров, когда они используются для постоянной проверки или опроса, чтобы узнать, изменилось ли что-то. В большинстве случаев существует push -эквивалент, когда вещь сообщает вам об изменении, когда оно происходит, поэтому вам не нужно постоянно проверять. Посмотрите, есть ли событие, которое достигает того же самого.
Несколько примеров:
- Если вам нужно знать, когда элемент попадает в область просмотра, используйте
IntersectionObserver
. - Если вам нужно знать, когда элемент меняет размер, используйте
ResizeObserver
. - Если вам нужно знать, когда меняется DOM, используйте
MutationObserver
или, возможно, обратные вызовы жизненного цикла пользовательского элемента . - Вместо опроса сервера рассмотрите веб-сокеты , события, отправляемые сервером , push-сообщения или выборку потоков .
- Если вам нужно реагировать на изменения сцены в аудио/видео, используйте такие события, как
timeupdate
иended
, илиrequestVideoFrameCallback
если вам нужно что-то сделать с каждым кадром.
Также есть триггеры уведомлений , если вы хотите показать уведомление в определенное время.
Анимация
Анимация — это визуальная вещь, поэтому она не должна использовать время процессора, когда страница скрыта .
requestAnimationFrame
гораздо лучше планирует работу анимации, чем таймеры JavaScript. Он синхронизируется с частотой обновления устройства, гарантируя, что вы получите только один обратный вызов на отображаемый кадр и получите максимальное количество времени для создания этого кадра. Кроме того, requestAnimationFrame
будет ждать, пока страница станет видимой, поэтому он не использует процессор, когда страница скрыта.
Если вы можете заранее объявить всю анимацию, рассмотрите возможность использования анимации CSS или API веб-анимации . Они имеют те же преимущества, что и requestAnimationFrame
, но браузер может выполнять дополнительные оптимизации, такие как автоматическое наложение композиции, и их, как правило, проще использовать.
Если ваша анимация имеет низкую частоту кадров (например, мигающий курсор), таймеры по-прежнему являются лучшим вариантом на данный момент, но вы можете объединить их с requestAnimationFrame
чтобы получить лучшее из обоих миров:
function animationInterval(ms, signal, callback) {
const start = document.timeline.currentTime;
function frame(time) {
if (signal.aborted) return;
callback(time);
scheduleFrame(time);
}
function scheduleFrame(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / ms) * ms;
const targetNext = start + roundedElapsed + ms;
const delay = targetNext - performance.now();
setTimeout(() => requestAnimationFrame(frame), delay);
}
scheduleFrame(start);
}
Использование:
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And stop it:
controller.abort();
Тестирование
Это изменение будет доступно для всех пользователей Chrome в Chrome 88 (январь 2021 г.). В настоящее время он доступен для 50% пользователей Chrome Beta, Dev и Canary. Если вы хотите протестировать его, используйте этот флаг командной строки при запуске Chrome Beta, Dev или Canary:
--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"
Аргумент grace_period_seconds/10
вызывает интенсивное регулирование через 10 секунд после скрытия страницы, а не через полные 5 минут, что позволяет легче увидеть влияние регулирования.
Будущее
Поскольку таймеры являются источником чрезмерной загрузки ЦП, мы продолжим искать способы их регулирования без нарушения работы веб-контента, а также API-интерфейсы, которые мы можем добавить/изменить в соответствии со сценариями использования. Лично я хотел бы устранить необходимость в animationInterval
в пользу эффективных низкочастотных обратных вызовов анимации. Если у вас есть какие-либо вопросы, пожалуйста , свяжитесь со мной в Твиттере !
Фотография в заголовке сделана Хизер Забриски на Unsplash .