Chrome 88 (styczeń 2021 r.) znacznie ograniczy łańcuchowe liczniki czasu JavaScriptu w przypadku ukrytych stron w określonych warunkach. Spowoduje to zmniejszenie wykorzystania procesora, co również ograniczy zużycie baterii. Są pewne przypadki, w których zachowanie się zmieni, ale zegary są często używane w miejscach, w których inny interfejs API byłby wydajniejszy i bardziej niezawodny.
OK, to było dość żargonowe i trochę niejednoznaczne. Zajmijmy się tą kwestią.
Terminologia
Ukryte strony
Ogólnie ukryta oznacza, że aktywna jest inna karta lub okno zostało zminimalizowane, ale przeglądarki mogą uznać, że strona jest ukryta, gdy jej zawartość jest całkowicie niewidoczna. Niektóre przeglądarki są w tej kwestii bardziej zaawansowane niż inne, ale zawsze możesz użyć interfejsu API do wykrywania widoczności strony, aby śledzić, kiedy przeglądarka uzna, że widoczność się zmieniła.
Wyzwalacze JavaScript
Przez timery JavaScript mam na myśli setTimeout
i setInterval
, które umożliwiają zaplanowanie połączenia zwrotnego na przyszłość. Czasomierze są przydatne i nie znikną, ale czasami służą do sprawdzania stanu, gdy zdarzenie byłoby bardziej wydajne i dokładniejsze.
Łańcuchowe liczniki czasu
Jeśli wywołasz funkcję setTimeout
w tym samym zadaniu jako funkcję zwracającą wywołanie setTimeout
, drugie wywołanie jest „połączone”. W przypadku setInterval
każda iteracja jest częścią łańcucha. Może być łatwiej zrozumieć to za pomocą kodu:
let chainCount = 0;
setInterval(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
}, 500);
Oraz:
let chainCount = 0;
function setTimeoutChain() {
setTimeout(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
setTimeoutChain();
}, 500);
}
Jak działa ograniczanie
Ograniczenie odbywa się etapami:
Minimalne ograniczanie
Dzieje się tak w przypadku ustawionych zegarów, gdy spełniony jest dowolny z tych warunków:
- Strona jest widoczna.
- Strona wygenerowała dźwięk w ciągu ostatnich 30 sekund. Możesz użyć dowolnego interfejsu API do generowania dźwięku, ale nieużyteczne są ścieżki audio bez dźwięku.
Timer nie jest ograniczany, chyba że żądany czas oczekiwania jest krótszy niż 4 ms, a liczba elementów łańcucha wynosi 5 lub więcej, w którym to przypadku czas oczekiwania jest ustawiony na 4 ms. To nie jest nowość – przeglądarki stosują tę metodę od wielu lat.
Ograniczenia
Dzieje się tak w przypadku minutników zaplanowanych, gdy minimalne ograniczenie nie ma zastosowania i spełniony jest co najmniej jeden z tych warunków:
- Liczba łańcuchów jest mniejsza niż 5.
- Strona była ukryta przez mniej niż 5 minut.
- Usługa WebRTC jest używana. Chodzi o
RTCPeerConnection
z wartością „otwarte”RTCDataChannel
lub „na żywo”MediaStreamTrack
.
Przeglądarka będzie sprawdzać zegary w tej grupie raz na sekundę. Są one sprawdzane tylko raz na sekundę, więc zegary z podobnym czasem trwania będą działać w grupach, co pozwoli zredukować czas, jaki karta potrzebuje na wykonanie kodu. To też nie jest nowością. przeglądarki od lat w pewnym stopniu tak właśnie działają.
Intensywne ograniczanie
Oto nowość w Chrome 88. Intensywne ograniczanie dotyczy zegarów, które są zaplanowane, gdy nie ma zastosowania żadne z warunków minimalnego ograniczania ani ograniczania, a wszystkie te warunki są spełnione:
- Strona jest ukryta od ponad 5 minut.
- Liczba ogniw wynosi co najmniej 5.
- strona nie emitowała dźwięku przez co najmniej 30 sekund;
- WebRTC nie jest używany.
W tym przypadku przeglądarka będzie sprawdzać zegary w tej grupie raz na minutę. Podobnie jak wcześniej oznacza to, że zegary będą sprawdzane zbiorczo w ramach minutowych kontroli.
Obejścia
Zazwyczaj istnieje lepsza alternatywa dla timera, a czasami można go połączyć z czymś innym, aby oszczędzać procesor i baterię.
Ankietowanie stanu
Jest to najczęstsze (nie)używanie timerów, które służą do ciągłego sprawdzania lub przepytywania, czy coś się zmieniło. W większości przypadków istnieje odpowiednik push, który informuje o zmianach w chwili ich wystąpienia, dzięki czemu nie musisz ich stale sprawdzać. Sprawdź, czy jest jakieś zdarzenie, które osiąga ten sam cel.
Oto kilka przykładów:
- Jeśli chcesz wiedzieć, kiedy element pojawia się w widocznym obszarze, użyj
IntersectionObserver
. - Jeśli chcesz wiedzieć, kiedy element zmienia rozmiar, użyj elementu
ResizeObserver
. - Jeśli chcesz wiedzieć, kiedy zmienia się DOM, użyj funkcji
MutationObserver
lub wyzwań zwrotnych cyklu życia elementu niestandardowego. - Zamiast sondować serwer, rozważ użycie gniazd internetowych, zdarzeń wysyłanych przez serwer, wiadomości typu push lub strumieni pobierania.
- Jeśli chcesz reagować na zmiany w efekcie dźwiękowym lub wideo, użyj zdarzeń takich jak
timeupdate
iended
lubrequestVideoFrameCallback
, jeśli chcesz wykonać jakąś czynność na każdym klatce.
Jeśli chcesz wyświetlić powiadomienie w określonym czasie, możesz też użyć wyzwalaczy powiadomień.
Animacja
Animacja jest elementem wizualnym, więc nie powinna zużywać czasu procesora, gdy strona jest ukryta.
requestAnimationFrame
znacznie lepiej planuje animacje niż liczniki czasu w JavaScript. Synchronizuje się z częstotliwością odświeżania urządzenia, dzięki czemu otrzymujesz tylko 1 wywołanie zwrotne na wyświetlaną klatkę i masz maksymalną ilość czasu na jej stworzenie. Ponadto requestAnimationFrame
będzie czekać, aż strona będzie widoczna, więc nie będzie używać procesora, gdy strona jest ukryta.
Jeśli możesz zdeklarować całą animację z wyprzedzeniem, rozważ użycie animacji CSS lub interfejsu API animacji internetowych. Mają one te same zalety co requestAnimationFrame
, ale przeglądarka może przeprowadzać dodatkowe optymalizacje, takie jak automatyczne łączenie. Są też zazwyczaj łatwiejsze w użyciu.
Jeśli animacja ma niską liczbę klatek na sekundę (np. migający kursor), obecnie nadal najlepszym rozwiązaniem są liczniki, ale możesz je połączyć z requestAnimationFrame
, aby uzyskać najlepsze efekty:
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);
}
Sposób użycia:
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And stop it:
controller.abort();
Testowanie
Ta zmiana zostanie włączona dla wszystkich użytkowników Chrome w wersji 88 (styczeń 2021 r.). Obecnie jest włączona dla 50% użytkowników Chrome w wersjach beta, deweloperskiej i Canary. Jeśli chcesz przetestować tę funkcję, podczas uruchamiania Chrome (wersji beta, deweloperskiej lub Canary) użyj tej flagi wiersza poleceń:
--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"
Argument grace_period_seconds/10
powoduje, że intensywne ograniczanie mocy obliczeniowej jest uruchamiane po 10 sekundach od ukrycia strony, a nie po 5 minutach, co ułatwia obserwowanie wpływu ograniczania mocy obliczeniowej.
Przyszłość
Ponieważ minutniki powodują nadmierne obciążenie procesora, będziemy nadal szukać sposobów na ich ograniczenie bez zakłócania treści w internecie oraz interfejsów API, które możemy dodać lub zmienić, aby spełniały wymagania dotyczące zastosowań. Osobiście chciałbym wyeliminować konieczność korzystania z funkcji animationInterval
na rzecz wydajnych wywołań animacji o niskiej częstotliwości. Jeśli masz pytania, skontaktuj się ze mną na Twitterze.
Zdjęcie w nagłówku: Heather Zabriskie, Unsplash.