1. Wprowadzenie
Interaktywna wersja demonstracyjna i ćwiczenia z programowania, które pozwalają dowiedzieć się więcej o interakcji do kolejnego wyrenderowania (INP).
Wymagania wstępne
- znajomość języków HTML i JavaScript;
- Zalecamy zapoznanie się z dokumentacją INP.
Czego się nauczysz
- Jak interakcje użytkowników i sposób ich obsługi wpływają na responsywność strony.
- Jak ograniczyć i wyeliminować opóźnienia, aby zapewnić użytkownikom płynne działanie.
Wymagania
- komputer z możliwością klonowania kodu z GitHuba i uruchamiania poleceń npm.
- Edytor tekstu.
- najnowszą wersję Chrome, aby wszystkie pomiary interakcji działały prawidłowo.
2. Konfiguracja
Pobieranie i uruchamianie kodu
Kod znajdziesz w repozytorium web-vitals-codelabs.
- Sklonuj repozytorium w terminalu:
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git - Przejdź do sklonowanego katalogu:
cd web-vitals-codelabs/understanding-inp - Zainstaluj zależności:
npm ci - Uruchom serwer WWW:
npm run start - W przeglądarce wejdź na http://localhost:5173/understanding-inp/.
Omówienie aplikacji
U góry strony znajduje się licznik Wynik i przycisk Zwiększ. Klasyczny przykład reaktywności i responsywności.

Poniżej przycisku znajdują się 4 pomiary:
- INP: bieżący wynik INP, który zwykle odpowiada najgorszej interakcji.
- Interakcja: wynik ostatniej interakcji.
- FPS: liczba klatek na sekundę w głównym wątku strony.
- Zegar: animacja działającego zegara, która pomaga zwizualizować zacinanie się.
Wpisy dotyczące liczby klatek na sekundę i licznika czasu nie są w ogóle potrzebne do pomiaru interakcji. Zostały dodane tylko po to, aby ułatwić wizualizację elastyczności.
Wypróbuj
Spróbuj kliknąć przycisk Zwiększ i zobacz, jak wynik rośnie. Czy wartości INP i Interakcja zmieniają się z każdym przyrostem?
INP mierzy czas, jaki upływa od momentu interakcji użytkownika do momentu, gdy strona faktycznie wyświetli mu wyrenderowaną aktualizację.
3. Pomiar interakcji za pomocą Narzędzi deweloperskich w Chrome
Otwórz Narzędzia deweloperskie z menu Więcej narzędzi > Narzędzia dla programistów, klikając stronę prawym przyciskiem myszy i wybierając Zbadaj lub używając skrótu klawiszowego.
Przejdź do panelu Wydajność, którego użyjesz do pomiaru interakcji.

Następnie zarejestruj interakcję w panelu Wydajność.
- Naciśnij przycisk nagrywania.
- Wejdź w interakcję ze stroną (kliknij przycisk Increment).
- Zatrzymaj nagrywanie.
Na osi czasu znajdziesz ścieżkę Interakcje. Rozwiń go, klikając trójkąt po lewej stronie.

Pojawią się 2 interakcje. Powiększ drugi, przewijając lub przytrzymując klawisz W.

Po najechaniu kursorem na interakcję zobaczysz, że była ona szybka, nie zajęła czasu na przetwarzanie i wymagała minimalnego czasu na opóźnienie wejściowe i opóźnienie prezentacji. Dokładne czasy będą zależeć od szybkości Twojego komputera.
4. Długo działające detektory zdarzeń
Otwórz plik index.js i odkomentuj funkcję blockFor w detektorze zdarzeń.
Zobacz pełny kod: click_block.html
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
});
Zapisz plik. Serwer wykryje zmianę i odświeży stronę.
Spróbuj ponownie wejść w interakcję ze stroną. Interakcje będą teraz zauważalnie wolniejsze.
Ślad wydajności
Zrób kolejne nagranie w panelu Wydajność, aby zobaczyć, jak to wygląda.

To, co kiedyś było krótką interakcją, teraz trwa całą sekundę.
Gdy najedziesz kursorem na interakcję, zauważysz, że czas jest prawie w całości poświęcony na „Czas przetwarzania”, czyli czas potrzebny na wykonanie wywołań zwrotnych detektora zdarzeń. Ponieważ blokujące wywołanie blockFor znajduje się w całości w detektorze zdarzeń, to właśnie tam jest zużywany czas.
5. Eksperyment: czas przetwarzania
Wypróbuj różne sposoby zmiany kolejności pracy detektora zdarzeń, aby zobaczyć, jak wpłynie to na INP.
Najpierw zaktualizuj interfejs
Co się stanie, jeśli zmienisz kolejność wywołań JS – najpierw zaktualizujesz interfejs, a potem zablokujesz?
Zobacz pełny kod: ui_first.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
blockFor(1000);
});
Czy interfejs pojawił się wcześniej? Czy kolejność wpływa na wyniki INP?
Spróbuj prześledzić i przeanalizować interakcję, aby sprawdzić, czy wystąpiły jakieś różnice.
Oddzielne detektory
Co się stanie, jeśli przeniesiesz zadanie do osobnego detektora zdarzeń? Zaktualizuj interfejs w jednym detektorze zdarzeń i zablokuj stronę w osobnym detektorze.
Zobacz pełny kod: two_click.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('click', () => {
blockFor(1000);
});
Jak to wygląda teraz w panelu wydajności?
Różne typy zdarzeń
Większość interakcji wywołuje wiele typów zdarzeń, od zdarzeń wskaźnika lub klawisza po najechanie kursorem, zaznaczenie/rozmycie i zdarzenia syntetyczne, takie jak beforechange i beforeinput.
Wiele rzeczywistych stron ma detektory wielu różnych zdarzeń.
Co się stanie, jeśli zmienisz typy zdarzeń dla detektorów zdarzeń? Na przykład zastąp jeden z detektorów zdarzeń click detektorem pointerup lub mouseup.
Zobacz pełny kod: diff_handlers.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('pointerup', () => {
blockFor(1000);
});
Brak aktualizacji interfejsu
Co się stanie, jeśli usuniesz wywołanie aktualizacji interfejsu z detektora zdarzeń?
Zobacz pełny kod: no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
6. Wyniki eksperymentu dotyczącego czasu przetwarzania
Śledzenie wydajności: najpierw zaktualizuj interfejs
Zobacz pełny kod: ui_first.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
blockFor(1000);
});
Jeśli spojrzysz na nagranie kliknięcia przycisku w panelu Wydajność, zobaczysz, że wyniki się nie zmieniły. Chociaż aktualizacja interfejsu została wywołana przed kodem blokującym, przeglądarka nie zaktualizowała tego, co zostało wyświetlone na ekranie, dopóki detektor zdarzeń nie zakończył działania, co oznacza, że interakcja nadal trwała nieco ponad sekundę.

Ślad wydajności: oddzielne detektory
Zobacz pełny kod: two_click.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('click', () => {
blockFor(1000);
});
Nie ma między nimi żadnej różnicy pod względem funkcjonalności. Interakcja nadal trwa całą sekundę.
Jeśli powiększysz interakcję kliknięcia, zobaczysz, że w wyniku zdarzenia click wywoływane są 2 różne funkcje.
Zgodnie z oczekiwaniami pierwsze zadanie – aktualizacja interfejsu – wykonuje się bardzo szybko, a drugie zajmuje całą sekundę. Jednak suma ich efektów powoduje, że użytkownik końcowy odczuwa takie samo opóźnienie interakcji.

Ślad wydajności: różne typy zdarzeń
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('pointerup', () => {
blockFor(1000);
});
Te wyniki są bardzo podobne. Interakcja nadal trwa całą sekundę. Jedyna różnica polega na tym, że krótszy detektor click, który aktualizuje tylko interfejs, działa teraz po blokującym detektorze pointerup.

Ślad wydajności: brak aktualizacji interfejsu
Zobacz pełny kod: no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
- Wynik nie jest aktualizowany, ale strona nadal się odświeża.
- Animacje, efekty CSS, domyślne działania komponentów internetowych (wprowadzanie danych w formularzu), wpisywanie tekstu i wyróżnianie tekstu są nadal aktualizowane.
W tym przypadku przycisk po kliknięciu przechodzi w stan aktywny i z powrotem, co wymaga wyrenderowania przez przeglądarkę, co oznacza, że nadal występuje INP.
Detektor zdarzeń zablokował wątek główny na sekundę, uniemożliwiając wyrenderowanie strony, więc interakcja nadal trwa całą sekundę.
Nagranie w panelu Wydajność pokazuje interakcję praktycznie identyczną z poprzednimi.

Na wynos
Dowolny kod uruchomiony w dowolnym detektorze zdarzeń opóźni interakcję.
- Obejmuje to detektory zarejestrowane z różnych skryptów oraz kod platformy lub biblioteki, który jest uruchamiany w detektorach, np. aktualizację stanu, która wywołuje renderowanie komponentu.
- Nie tylko własny kod, ale też wszystkie skrypty innych firm.
To częsty problem.
Na koniec: to, że Twój kod nie wywołuje renderowania, nie oznacza, że renderowanie nie będzie czekać na zakończenie powolnych detektorów zdarzeń.
7. Eksperyment: opóźnienie wejściowe
A co z długo działającym kodem poza detektorami zdarzeń? Na przykład:
- Jeśli masz element
<script>, który wczytuje się późno i losowo blokuje stronę podczas wczytywania. - wywołanie interfejsu API, np.
setInterval, które okresowo blokuje stronę?
Spróbuj usunąć blockFor z detektora zdarzeń i dodać do setInterval():
Zobacz pełny kod: input_delay.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
Co się dzieje
8. Wyniki eksperymentu dotyczącego opóźnienia wejściowego
Zobacz pełny kod: input_delay.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
Zarejestrowanie kliknięcia przycisku, które nastąpiło podczas wykonywania zadania blokującego setInterval, powoduje długotrwałą interakcję, nawet jeśli w jej trakcie nie jest wykonywana żadna praca blokująca.
Te długie okresy są często nazywane długimi zadaniami.
Po najechaniu kursorem na interakcję w Narzędziach deweloperskich zobaczysz, że czas interakcji jest teraz przypisywany głównie do opóźnienia wejściowego, a nie do czasu przetwarzania.

Zauważ, że nie zawsze wpływa to na interakcje. Jeśli nie klikniesz, gdy zadanie jest uruchomione, możesz mieć szczęście. Takie „losowe” kichnięcia mogą być koszmarem do debugowania, gdy tylko czasami powodują problemy.
Jednym ze sposobów na ich wykrycie jest pomiar długich zadań (lub długich klatek animacji) i łącznego czasu blokowania.
9. Powolna prezentacja
Do tej pory przyjrzeliśmy się wydajności JavaScriptu, opóźnieniu wejściowemu i detektorom zdarzeń, ale co jeszcze wpływa na renderowanie kolejnego wyrenderowania?
Aktualizując stronę o kosztowne efekty.
Nawet jeśli aktualizacja strony następuje szybko, przeglądarka może mieć trudności z ich wyrenderowaniem.
W wątku głównym:
- platformy UI, które muszą renderować aktualizacje po zmianach stanu;
- Zmiany DOM lub przełączanie wielu kosztownych selektorów zapytań CSS mogą wywołać wiele operacji związanych ze stylem, układem i renderowaniem.
Poza wątkiem głównym:
- Używanie CSS do obsługi efektów GPU
- dodawanie bardzo dużych obrazów w wysokiej rozdzielczości,
- Używanie SVG/Canvas do rysowania złożonych scen

Oto kilka przykładów, które można znaleźć w internecie:
- Witryna SPA, która po kliknięciu linku przebudowuje cały DOM bez przerwy na wyświetlenie początkowej reakcji wizualnej.
- Strona wyszukiwania, która oferuje złożone filtry wyszukiwania z dynamicznym interfejsem, ale wymaga do tego kosztownych detektorów.
- Przełącznik trybu ciemnego, który zmienia styl i układ całej strony.
10. Eksperyment: opóźnienie prezentacji
Wolny nośnik requestAnimationFrame
Zasymulujmy długie opóźnienie prezentacji za pomocą interfejsu API requestAnimationFrame().
Przenieś wywołanie blockFor do wywołania zwrotnego requestAnimationFrame, aby było uruchamiane po zwróceniu detektora zdarzeń:
Zobacz pełny kod: presentation_delay.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
Co się dzieje
11. Wyniki eksperymentu dotyczącego opóźnienia prezentacji
Zobacz pełny kod: presentation_delay.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
Interakcja nadal trwa sekundę, więc co się stało?
requestAnimationFrame żąda wywołania zwrotnego przed następnym renderowaniem. Ponieważ INP mierzy czas od interakcji do kolejnego wyrenderowania, blockFor(1000) w requestAnimationFrame nadal blokuje kolejne wyrenderowanie przez całą sekundę.

Zwróć jednak uwagę na 2 rzeczy:
- Po najechaniu kursorem zobaczysz, że cały czas interakcji jest teraz spędzany na „opóźnieniu prezentacji”, ponieważ blokowanie wątku głównego następuje po zwróceniu detektora zdarzeń.
- Głównym działaniem w głównym wątku nie jest już zdarzenie kliknięcia, ale „Uruchomiono klatkę animacji”.
12. Diagnozowanie interakcji
Na tej stronie testowej responsywność jest bardzo widoczna dzięki wynikom, licznikom czasu i licznikowi w interfejsie, ale podczas testowania przeciętnej strony jest to bardziej subtelne.
Gdy interakcje trwają długo, nie zawsze wiadomo, co jest tego przyczyną. Czy to:
- Opóźnienie wejściowe?
- Czas przetwarzania zdarzenia?
- Opóźnienie prezentacji?
Na dowolnej stronie możesz użyć Narzędzi deweloperskich, aby zmierzyć czas reakcji. Aby wyrobić sobie nawyk, wypróbuj ten proces:
- Przeglądaj internet w zwykły sposób.
- Obserwuj dziennik interakcji w widoku wskaźników na żywo w panelu Wydajność w Narzędziach deweloperskich.
- Jeśli zauważysz interakcję o niskiej skuteczności, spróbuj ją powtórzyć:
- Jeśli nie możesz go powtórzyć, użyj dziennika interakcji, aby uzyskać statystyki.
- Jeśli możesz go powtórzyć, nagraj ślad w panelu Wydajność.
Wszystkie opóźnienia
Spróbuj dodać do strony trochę wszystkich tych problemów:
Zobacz pełny kod: all_the_things.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
Następnie użyj konsoli i panelu wydajności, aby zdiagnozować problemy.
13. Eksperyment: praca asynchroniczna
W ramach interakcji możesz uruchamiać efekty niewizualne, takie jak wysyłanie żądań sieciowych, uruchamianie liczników czasu czy aktualizowanie stanu globalnego. Co się stanie, gdy te efekty w końcu zaktualizują stronę?
Jeśli po interakcji kolejne wyrenderowanie może się odbyć, nawet jeśli przeglądarka zdecyduje, że nie potrzebuje nowej aktualizacji renderowania, pomiar interakcji zostanie zatrzymany.
Aby to sprawdzić, kontynuuj aktualizowanie interfejsu z detektora kliknięć, ale uruchom zadanie blokujące z limitu czasu.
Zobacz pełny kod: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
Co dalej?
14. Wyniki eksperymentu dotyczącego pracy asynchronicznej
Zobacz pełny kod: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});

Interakcja jest teraz krótka, ponieważ główny wątek jest dostępny natychmiast po zaktualizowaniu interfejsu. Długie zadanie blokujące nadal jest wykonywane, ale dopiero po wyrenderowaniu, dzięki czemu użytkownik natychmiast otrzymuje informacje zwrotne w interfejsie.
Wniosek: jeśli nie możesz usunąć, przynajmniej przenieś.
Metody
Czy możemy uzyskać lepszy wynik niż stałe 100 milisekund setTimeout? Prawdopodobnie nadal chcemy, aby kod działał jak najszybciej, bo inaczej po prostu byśmy go usunęli.
Cel:
- Interakcja zostanie uruchomiona
incrementAndUpdateUI(). blockFor()zostanie uruchomiony tak szybko, jak to możliwe, ale nie zablokuje następnego renderowania.- Zapewnia to przewidywalne działanie bez „magicznych limitów czasu”.
Możesz to zrobić na kilka sposobów:
setTimeout(0)Promise.then()requestAnimationFramerequestIdleCallbackscheduler.postTask()
„requestPostAnimationFrame”
W przeciwieństwie do samej funkcji requestAnimationFrame (która próbuje uruchomić się przed kolejnym wyrenderowaniem i zwykle powoduje powolną interakcję), requestAnimationFrame + setTimeout tworzy prosty polyfill dla requestPostAnimationFrame, uruchamiając wywołanie zwrotne po kolejnym wyrenderowaniu.
Zobacz pełny kod: raf+task.html
function afterNextPaint(callback) {
requestAnimationFrame(() => {
setTimeout(callback, 0);
});
}
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
afterNextPaint(() => {
blockFor(1000);
});
});
Dla wygody możesz nawet opakować go w obietnicę:
Zobacz pełny kod: raf+task2.html
async function nextPaint() {
return new Promise(resolve => afterNextPaint(resolve));
}
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
await nextPaint();
blockFor(1000);
});
15. Wiele interakcji (i kliknięć ze złością)
Przeniesienie długich zadań blokujących może pomóc, ale te długie zadania nadal blokują stronę, co wpływa na przyszłe interakcje, a także na wiele innych animacji i aktualizacji strony.
Ponownie wypróbuj wersję strony z asynchronicznym blokowaniem (lub własną, jeśli w ostatnim kroku udało Ci się wymyślić własną odmianę odraczania pracy):
Zobacz pełny kod: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
Co się stanie, jeśli klikniesz kilka razy szybko?
Ślad wydajności
Każde kliknięcie powoduje dodanie do kolejki zadania o czasie trwania 1 sekundy, co zapewnia, że wątek główny jest blokowany przez znaczną ilość czasu.

Gdy te długie zadania nakładają się na nowe kliknięcia, powoduje to powolne interakcje, mimo że sam detektor zdarzeń zwraca wynik niemal natychmiast. Stworzyliśmy taką samą sytuację jak we wcześniejszym eksperymencie z opóźnieniami wejściowymi. Tylko tym razem opóźnienie wejściowe nie pochodzi z setInterval, ale z pracy wywołanej przez wcześniejsze detektory zdarzeń.
Strategie
Najlepiej byłoby całkowicie usunąć długie zadania.
- Usuń całkowicie niepotrzebny kod, zwłaszcza skrypty.
- Zoptymalizuj kod, aby uniknąć wykonywania długich zadań.
- Przerwij nieaktualne zadania, gdy pojawią się nowe interakcje.
16. Strategia 1. Debounce
Klasyczna strategia. Jeśli interakcje następują szybko po sobie, a ich przetwarzanie lub efekty sieciowe są kosztowne, celowo opóźnij rozpoczęcie pracy, aby móc anulować i ponownie uruchomić proces. Ten wzorzec jest przydatny w przypadku interfejsów użytkownika, takich jak pola autouzupełniania.
- Użyj
setTimeout, aby opóźnić rozpoczęcie kosztownej pracy, na przykład o 500–1000 milisekund. - Zapisz identyfikator minutnika.
- Jeśli pojawi się nowa interakcja, anuluj poprzedni minutnik za pomocą funkcji
clearTimeout.
Zobacz pełny kod: debounce.html
let timer;
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
blockFor(1000);
}, 1000);
});
Ślad wydajności

Mimo wielokrotnych kliknięć uruchamiane jest tylko jedno zadanie blockFor, które czeka na uruchomienie, dopóki przez całą sekundę nie będzie żadnych kliknięć. W przypadku interakcji, które występują w seriach, takich jak wpisywanie tekstu lub cele elementów, które mają być wielokrotnie klikane, jest to idealna strategia do użycia domyślnie.
17. Strategia 2. Przerwanie długotrwałego zadania
Nadal istnieje ryzyko, że kolejne kliknięcie nastąpi tuż po upływie okresu debounce, w środku długiego zadania, i stanie się bardzo powolną interakcją z powodu opóźnienia wejściowego.
Jeśli interakcja pojawi się w trakcie wykonywania zadania, najlepiej wstrzymać pracę, aby od razu obsłużyć nowe interakcje. Jak możemy to zrobić?
Istnieją interfejsy API takie jak isInputPending, ale ogólnie lepiej jest podzielić długie zadania na mniejsze części.
Dużo: setTimeout
Pierwsza próba: zrób coś prostego.
Zobacz pełny kod: small_tasks.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
});
});
Dzięki temu przeglądarka może zaplanować każde zadanie osobno, a dane wejściowe mogą mieć wyższy priorytet.

Wracamy do pełnych 5 sekund pracy na 5 kliknięć, ale każde 1-sekundowe zadanie na kliknięcie zostało podzielone na 10 zadań po 100 milisekund. W rezultacie – nawet przy wielu interakcjach nakładających się na te zadania – żadna interakcja nie ma opóźnienia wejściowego dłuższego niż 100 milisekund. Przeglądarka traktuje priorytetowo przychodzące detektory zdarzeń, a nie zadanie setTimeout, dzięki czemu interakcje pozostają responsywne.
Ta strategia sprawdza się szczególnie dobrze, gdy planujesz osobne punkty wejścia – na przykład gdy masz wiele niezależnych funkcji, które musisz wywołać podczas wczytywania aplikacji. Samo wczytywanie skryptów i uruchamianie wszystkiego w czasie oceny skryptu może domyślnie spowodować uruchomienie wszystkiego w ramach jednego gigantycznego długiego zadania.
Ta strategia nie sprawdza się jednak w przypadku rozdzielania ściśle sprzężonego kodu, takiego jak pętla for, która używa stanu współdzielonego.
Teraz z: yield()
Możemy jednak wykorzystać nowoczesne funkcje async i await, aby łatwo dodawać „punkty wstrzymania” do dowolnej funkcji JavaScriptu.
Na przykład:
Zobacz pełny kod: yieldy.html
// Polyfill for scheduler.yield()
async function schedulerDotYield() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function blockInPiecesYieldy(ms) {
const ms_per_part = 10;
const parts = ms / ms_per_part;
for (let i = 0; i < parts; i++) {
await schedulerDotYield();
blockFor(ms_per_part);
}
}
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
await blockInPiecesYieldy(1000);
});
Tak jak wcześniej, wątek główny jest przekazywany po wykonaniu części pracy, a przeglądarka może reagować na wszelkie przychodzące interakcje, ale teraz wystarczy tylko await schedulerDotYield() zamiast oddzielnych setTimeout, co sprawia, że jest to wystarczająco ergonomiczne, aby można było używać tego rozwiązania nawet w środku pętli for.
Teraz z: AbortContoller()
To działa, ale każda interakcja planuje więcej pracy, nawet jeśli pojawiły się nowe interakcje, które mogły zmienić pracę do wykonania.
W przypadku strategii debouncingu anulowaliśmy poprzedni limit czasu przy każdej nowej interakcji. Czy możemy zrobić coś podobnego tutaj? Możesz to zrobić, używając AbortController():
Zobacz pełny kod: aborty.html
// Polyfill for scheduler.yield()
async function schedulerDotYield() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function blockInPiecesYieldyAborty(ms, signal) {
const parts = ms / 10;
for (let i = 0; i < parts; i++) {
// If AbortController has been asked to stop, abandon the current loop.
if (signal.aborted) return;
await schedulerDotYield();
blockFor(10);
}
}
let abortController = new AbortController();
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
abortController.abort();
abortController = new AbortController();
await blockInPiecesYieldyAborty(1000, abortController.signal);
});
Gdy nastąpi kliknięcie, rozpoczyna się pętla blockInPiecesYieldyAborty for, która wykonuje wszystkie niezbędne zadania, okresowo oddając główny wątek, dzięki czemu przeglądarka pozostaje responsywna na nowe interakcje.
Gdy nastąpi drugie kliknięcie, pierwsza pętla zostanie oznaczona jako anulowana za pomocą AbortController i rozpocznie się nowa pętla blockInPiecesYieldyAborty. Gdy pierwsza pętla zostanie zaplanowana do ponownego uruchomienia, zauważy, że signal.aborted ma teraz wartość true, i natychmiast powróci bez wykonywania dalszych działań.

18. Podsumowanie
Podzielenie wszystkich długich zadań pozwala witrynie reagować na nowe interakcje. Dzięki temu możesz szybko przekazać wstępne opinie, a także podejmować decyzje, takie jak przerwanie pracy w toku. Czasami oznacza to planowanie punktów wejścia jako osobnych zadań. Czasami oznacza to dodanie punktów „ustąp pierwszeństwa” w dogodnych miejscach.
Pamiętaj
- INP mierzy wszystkie interakcje.
- Każda interakcja jest mierzona od wprowadzenia danych do kolejnego wyrenderowania – w ten sposób użytkownik widzi responsywność.
- Opóźnienie wejściowe, czas przetwarzania zdarzenia i opóźnienie prezentacji wpływają na czas reakcji na interakcję.
- W Narzędziach deweloperskich możesz łatwo mierzyć INP i zestawienia interakcji.
Strategie
- Nie umieszczaj na stronach długo działającego kodu (długich zadań).
- Przenieś niepotrzebny kod z detektorów zdarzeń do momentu po następnym renderowaniu.
- Upewnij się, że aktualizacja renderowania jest wydajna dla przeglądarki.