1. Wprowadzenie
To interaktywne ćwiczenie z programowania, z którego dowiesz się, jak mierzyć czas od interakcji do kolejnego wyrenderowania (INP) za pomocą biblioteki web-vitals.
Wymagania wstępne
- Znajomość języków HTML i JavaScript.
- Zalecane: przeczytaj dokumentację dotyczącą wskaźnika INP na web.dev.
Czego się nauczysz
- Jak dodać bibliotekę
web-vitalsdo strony i używać jej danych atrybucji. - Użyj danych atrybucji, aby zdiagnozować, gdzie i jak zacząć poprawiać INP.
Co będzie potrzebne
- Komputer z możliwością klonowania kodu z GitHuba i uruchamiania poleceń npm.
- edytor tekstu,
- najnowszej wersji 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/measuring-inp. - Zainstaluj zależności:
npm ci. - Uruchom serwer WWW:
npm run start - W przeglądarce otwórz adres http://localhost:8080/.
Wypróbuj stronę
W tym laboratorium kodowania używamy Gastropodicon (popularnej witryny referencyjnej dotyczącej anatomii ślimaków), aby zbadać potencjalne problemy z INP.

Spróbuj wejść w interakcję ze stroną, aby sprawdzić, które interakcje są powolne.
3. Wprowadzenie do 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.
W tym module użyjemy zarówno panelu Skuteczność, jak i Konsoli. W każdej chwili możesz się między nimi przełączać, korzystając z kart u góry Narzędzi deweloperskich.
- Problemy z INP najczęściej występują na urządzeniach mobilnych, dlatego przełącz się na emulację wyświetlania na urządzeniach mobilnych.
- Jeśli testujesz na komputerze stacjonarnym lub laptopie, wydajność będzie prawdopodobnie znacznie lepsza niż na rzeczywistym urządzeniu mobilnym. Aby uzyskać bardziej realistyczny obraz wydajności, kliknij ikonę koła zębatego w prawym górnym rogu panelu Wydajność, a następnie wybierz 4-krotne spowolnienie procesora.

4. Instalowanie biblioteki web-vitals
web-vitals to biblioteka JavaScriptu do pomiaru wskaźników Web Vitals, które mają wpływ na wrażenia użytkowników. Możesz użyć tej biblioteki do przechwytywania tych wartości, a następnie wysyłać je do punktu końcowego Analytics w celu późniejszej analizy, aby określić, kiedy i gdzie występują wolne interakcje.
Istnieje kilka sposobów dodawania biblioteki do strony. Sposób instalacji biblioteki w Twojej witrynie zależy od sposobu zarządzania zależnościami, procesu kompilacji i innych czynników. Więcej informacji o dostępnych opcjach znajdziesz w dokumentacji biblioteki.
W tym ćwiczeniu zainstalujemy skrypt z npm i załadujemy go bezpośrednio, aby uniknąć zagłębiania się w konkretny proces kompilacji.
Możesz używać 2 wersji web-vitals:
- Wersji „standardowej” należy używać, jeśli chcesz śledzić wartości podstawowych wskaźników internetowych podczas wczytywania strony.
- Wersja „atrybucja” dodaje do każdego rodzaju danych dodatkowe informacje do debugowania, które pomagają zdiagnozować, dlaczego dany rodzaj danych ma taką wartość.
W tym ćwiczeniu chcemy mierzyć INP za pomocą atrybucji.
Dodaj web-vitals do devDependencies projektu, uruchamiając npm install -D web-vitals
Dodaj web-vitals do strony:
Dodaj wersję skryptu do atrybucji na końcu pliku index.html i zarejestruj wyniki w konsoli:
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log);
</script>
Wypróbuj
Spróbuj ponownie wejść w interakcję ze stroną przy otwartej konsoli. Gdy klikasz różne elementy na stronie, nic nie jest rejestrowane.
Wartość INP jest mierzona w całym cyklu życia strony, więc domyślnie web-vitals nie raportuje INP, dopóki użytkownik nie opuści lub nie zamknie strony. Jest to idealne zachowanie w przypadku wysyłania sygnałów do usług takich jak analityka, ale mniej przydatne w przypadku interaktywnego debugowania.
web-vitals udostępnia opcję reportAllChanges, która umożliwia bardziej szczegółowe raportowanie. Gdy ta opcja jest włączona, nie jest rejestrowana każda interakcja, ale każda interakcja wolniejsza od poprzedniej.
Spróbuj dodać opcję do skryptu i ponownie wejść w interakcję ze stroną:
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log, {reportAllChanges: true});
</script>
Odśwież stronę. Interakcje powinny być teraz zgłaszane do konsoli i aktualizowane za każdym razem, gdy pojawi się nowa najwolniejsza interakcja. Spróbuj na przykład wpisać coś w polu wyszukiwania, a następnie usunąć wpisane słowa.

5. Co zawiera atrybucja?
Zacznijmy od pierwszej interakcji, z którą większość użytkowników będzie miała do czynienia na stronie, czyli okna dialogowego zgody na stosowanie plików cookie.
Na wielu stronach znajdują się skrypty, które wymagają synchronicznego wywoływania plików cookie, gdy użytkownik zaakceptuje pliki cookie. Powoduje to, że kliknięcie staje się wolną interakcją. Tak właśnie jest w tym przypadku.
Kliknij Tak, aby zaakceptować pliki cookie (wersja demonstracyjna), i sprawdź dane INP, które zostały właśnie zarejestrowane w konsoli Narzędzi deweloperskich.

Te informacje najwyższego poziomu są dostępne w wersjach standardowej i atrybucyjnej wskaźników internetowych:
{
name: 'INP',
value: 344,
rating: 'needs-improvement',
entries: [...],
id: 'v4-1715732159298-8028729544485',
navigationType: 'reload',
attribution: {...},
}
Czas od kliknięcia przez użytkownika do kolejnego wyrenderowania wyniósł 344 milisekundy, co oznacza wartość INP „wymaga poprawy”. Tablica entries zawiera wszystkie wartości PerformanceEntry powiązane z tą interakcją – w tym przypadku tylko jedno zdarzenie kliknięcia.
Aby dowiedzieć się, co się wtedy dzieje, najbardziej interesuje nas właściwość attribution. Aby utworzyć dane atrybucji, web-vitals sprawdza, która długa animacja pokrywa się ze zdarzeniem kliknięcia. Wartość LoAF może następnie dostarczyć szczegółowe dane o tym, jak spędzono czas w tej ramce, od uruchomionych skryptów po czas spędzony w requestAnimationFrame wywołaniu zwrotnym, stylu i układzie.
Rozwiń właściwość attribution, aby wyświetlić więcej informacji. Dane są znacznie bardziej szczegółowe.
attribution: {
interactionTargetElement: Element,
interactionTarget: '#confirm',
interactionType: 'pointer',
inputDelay: 27,
processingDuration: 295.6,
presentationDelay: 21.4,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
Najpierw podawane są informacje o tym, z czym użytkownik wszedł w interakcję:
interactionTargetElement: odniesienie na żywo do elementu, z którym użytkownik wszedł w interakcję (jeśli element nie został usunięty z DOM).interactionTarget: selektor do znajdowania elementu na stronie.
Następnie podajemy ogólny harmonogram:
inputDelay: czas od rozpoczęcia interakcji przez użytkownika (np. kliknięcia myszą) do momentu, w którym zaczął działać detektor zdarzeń dla tej interakcji. W tym przypadku opóźnienie wejściowe wynosiło tylko około 27 milisekund, nawet przy włączonym ograniczaniu procesora.processingDuration: czas potrzebny na wykonanie detektorów zdarzeń. Często strony mają wielu odbiorców jednego zdarzenia (np.pointerdown,pointerupiclick). Jeśli wszyscy działają w tej samej klatce animacji, zostaną połączone w tym czasie. W tym przypadku czas przetwarzania wynosi 295,6 milisekundy, czyli większość czasu INP.presentationDelay: czas od zakończenia działania detektorów zdarzeń do momentu, w którym przeglądarka zakończy renderowanie następnej ramki. W tym przypadku jest to 21, 4 milisekundy.
Te fazy INP mogą być istotnym sygnałem do diagnozowania, co należy zoptymalizować. Więcej informacji na ten temat znajdziesz w przewodniku Optymalizacja INP.
Głębiej analizując, tablica processedEventEntries zawiera 5 zdarzeń, w przeciwieństwie do pojedynczego zdarzenia w tablicy INP najwyższego poziomu entries. Na czym polega różnica?
processedEventEntries: [
{
name: 'mouseover',
entryType: 'event',
startTime: 1801.6,
duration: 344,
processingStart: 1825.3,
processingEnd: 1825.3,
cancelable: true
},
{
name: 'mousedown',
entryType: 'event',
startTime: 1801.6,
duration: 344,
processingStart: 1825.3,
processingEnd: 1825.3,
cancelable: true
},
{name: 'mousedown', ...},
{name: 'mouseup', ...},
{name: 'click', ...},
],
Najwyższy poziom to zdarzenie the INP, w tym przypadku kliknięcie. Atrybucja processedEventEntries to wszystkie zdarzenia, które zostały przetworzone w tej samej ramce. Zwróć uwagę, że zawiera on inne zdarzenia, takie jak mouseover i mousedown, a nie tylko zdarzenie kliknięcia. Informacje o tych zdarzeniach mogą być bardzo ważne, jeśli również przebiegały powoli, ponieważ wszystkie przyczyniły się do powolnej reakcji.
Jest jeszcze tablica longAnimationFrameEntries. Może to być pojedynczy wpis, ale w niektórych przypadkach interakcja może obejmować wiele klatek. Mamy tu najprostszy przypadek z jedną długą klatką animacji.
longAnimationFrameEntries
Rozwijanie wpisu LoAF:
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 1823,
duration: 319,
renderStart: 2139.5,
styleAndLayoutStart: 2139.7,
firstUIEventTimestamp: 1801.6,
blockingDuration: 268,
scripts: [{...}]
}],
Znajdziesz tu wiele przydatnych wartości, np. czas poświęcony na stylizację. Więcej informacji o tych właściwościach znajdziesz w artykule o interfejsie Long Animation Frames API. Obecnie interesuje nas głównie właściwość scripts, która zawiera wpisy z informacjami o skryptach odpowiedzialnych za długotrwałą klatkę:
scripts: [{
name: 'script',
invoker: 'BUTTON#confirm.onclick',
invokerType: 'event-listener',
startTime: 1828.6,
executionStart: 1828.6,
duration: 294,
sourceURL: 'http://localhost:8080/third-party/cmp.js',
sourceFunctionName: '',
sourceCharPosition: 1144
}]
W tym przypadku możemy stwierdzić, że czas był spędzany głównie w event-listener, wywoływanym w BUTTON#confirm.onclick. Możemy nawet zobaczyć adres URL źródła skryptu i pozycję znaku, w którym zdefiniowano funkcję.
Na wynos
Co można ustalić na podstawie tych danych o atrybucji?
- Interakcja została wywołana przez kliknięcie elementu
button#confirm(zattribution.interactionTargeti właściwościinvokerw pozycji atrybucji skryptu). - Czas był poświęcony głównie na wykonywanie funkcji nasłuchujących zdarzeń (
attribution.processingDurationw porównaniu z całkowitą wartościąvalue). - Kod detektora powolnych zdarzeń zaczyna się od detektora kliknięć zdefiniowanego w pliku
third-party/cmp.js(odscripts.sourceURL).
To wystarczająca ilość danych, aby wiedzieć, gdzie musimy przeprowadzić optymalizację.
6. Wiele detektorów zdarzeń
Odśwież stronę, aby wyczyścić konsolę Narzędzi deweloperskich i sprawdzić, czy interakcja związana ze zgodą na stosowanie plików cookie nie jest już najdłuższą interakcją.
Zacznij pisać w polu wyszukiwania. Co pokazują dane atrybucji? Jak myślisz, co się może dziać?
Dane atrybucji
Najpierw przyjrzyjmy się ogólnie jednemu z przykładów testowania wersji demonstracyjnej:
{
name: 'INP',
value: 1072,
rating: 'poor',
attribution: {
interactionTargetElement: Element,
interactionTarget: '#search-terms',
interactionType: 'keyboard',
inputDelay: 3.3,
processingDuration: 1060.6,
presentationDelay: 8.1,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
}
Jest to słaba wartość INP (przy włączonym ograniczaniu przepustowości procesora) pochodząca z interakcji z elementem input#search-terms za pomocą klawiatury. Większość czasu (1061 milisekund z całkowitego INP wynoszącego 1072 milisekundy) została poświęcona na czas przetwarzania.
Wpisy scripts są jednak ciekawsze.
Przeskakiwanie układu
Pierwszy wpis w tablicy scripts dostarcza nam cennych informacji:
scripts: [{
name: 'script',
invoker: 'BUTTON#confirm.onclick',
invokerType: 'event-listener',
startTime: 4875.6,
executionStart: 4875.6,
duration: 497,
forcedStyleAndLayoutDuration: 388,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: 'handleSearch',
sourceCharPosition: 940
},
...]
Większość czasu przetwarzania przypada na wykonanie tego skryptu, który jest input detektorem (wywołującym jest INPUT#search-terms.oninput). Podana jest nazwa funkcji (handleSearch), a także pozycja znaku w index.js pliku źródłowym.
Jest jednak nowa właściwość: forcedStyleAndLayoutDuration. Jest to czas spędzony na wywołaniu skryptu, podczas którego przeglądarka musiała ponownie rozmieścić elementy na stronie. Innymi słowy, 78% czasu – 388 milisekund z 497 – poświęconego na wykonanie tego detektora zdarzeń zostało w rzeczywistości zmarnowane na thrashing układu.
Naprawienie tego błędu powinno być priorytetem.
Powracający słuchacze
Same w sobie kolejne 2 wpisy skryptu nie są niczym szczególnym:
scripts: [...,
{
name: 'script',
invoker: '#document.onkeyup',
invokerType: 'event-listener',
startTime: 5375.3,
executionStart: 5375.3,
duration: 124,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: '',
sourceCharPosition: 1526,
},
{
name: 'script',
invoker: '#document.onkeyup',
invokerType: 'event-listener',
startTime: 5673.9,
executionStart: 5673.9,
duration: 95,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: '',
sourceCharPosition: 1526
}]
Oba wpisy to keyup, które są wykonywane jeden po drugim. Słuchacze są funkcjami anonimowymi (dlatego w sourceFunctionName nie ma żadnych informacji), ale nadal mamy plik źródłowy i pozycję znaku, więc możemy znaleźć miejsce, w którym znajduje się kod.
Dziwne jest to, że oba pochodzą z tego samego pliku źródłowego i pozycji znaku.
Przeglądarka przetworzyła wiele naciśnięć klawiszy w jednej klatce animacji, co spowodowało, że ten detektor zdarzeń został uruchomiony 2 razy, zanim cokolwiek zostało narysowane.
Ten efekt może się też kumulować: im dłużej trwa przetwarzanie zdarzeń, tym więcej dodatkowych zdarzeń wejściowych może się pojawić, co jeszcze bardziej wydłuża powolną interakcję.
Ponieważ jest to interakcja wyszukiwania/autouzupełniania, dobrym rozwiązaniem będzie eliminacja drgań klawiszy, tak aby na klatkę przetwarzane było co najwyżej jedno naciśnięcie klawisza.
7. Opóźnienie wejściowe
Typową przyczyną opóźnień w reakcji na działanie użytkownika – czasu od interakcji użytkownika do momentu, w którym detektor zdarzeń może rozpocząć przetwarzanie interakcji – jest zajęty wątek główny. Może to mieć kilka przyczyn:
- Strona się ładuje, a wątek główny jest zajęty wykonywaniem początkowych czynności związanych z konfigurowaniem DOM, układem i stylem strony oraz ocenianiem i uruchamianiem skryptów.
- Strona jest zwykle zajęta, np. wykonuje obliczenia, animacje oparte na skryptach lub wyświetla reklamy.
- Przetwarzanie poprzednich interakcji trwa tak długo, że opóźnia kolejne interakcje, co było widoczne w ostatnim przykładzie.
Strona demonstracyjna ma tajną funkcję: jeśli klikniesz logo ślimaka u góry strony, zacznie się animować i wykonywać intensywną pracę JavaScriptu w głównym wątku.
- Aby rozpocząć animację, kliknij logo ślimaka.
- Zadania JavaScript są wywoływane, gdy ślimak znajduje się na dole odbicia. Spróbuj wejść w interakcję ze stroną jak najbliżej końca sesji i sprawdź, jak wysoki INP możesz wywołać.
Na przykład nawet jeśli nie wywołasz żadnych innych detektorów zdarzeń (np. klikając i skupiając się na polu wyszukiwania w momencie, gdy ślimak się odbija), praca wątku głównego spowoduje, że strona będzie przez zauważalny czas nie reagować.
Na wielu stronach intensywna praca w głównym wątku nie będzie tak dobrze zorganizowana, ale ten przykład dobrze pokazuje, jak można ją zidentyfikować w danych atrybucji INP.
Oto przykład atrybucji, która uwzględnia tylko pole wyszukiwania podczas odbijania się ślimaka:
{
name: 'INP',
value: 728,
rating: 'poor',
attribution: {
interactionTargetElement: Element,
interactionTarget: '#search-terms',
interactionType: 'pointer',
inputDelay: 702.3,
processingDuration: 4.9,
presentationDelay: 20.8,
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 2064.8,
duration: 790,
renderStart: 2065,
styleAndLayoutStart: 2854.2,
firstUIEventTimestamp: 0,
blockingDuration: 740,
scripts: [{...}]
}]
}
}
Zgodnie z przewidywaniami detektory zdarzeń zostały wykonane szybko – czas przetwarzania wyniósł 4,9 milisekundy, a większość czasu słabej interakcji zajęło opóźnienie wejścia, które wyniosło 702,3 milisekundy z całkowitego czasu 728 milisekund.
W takiej sytuacji trudno jest znaleźć przyczynę problemu. Wiemy, z czym i w jaki sposób użytkownik wszedł w interakcję, ale wiemy też, że ta część interakcji przebiegła szybko i nie stanowiła problemu. Zamiast tego coś innego na stronie opóźniło rozpoczęcie przetwarzania interakcji, ale skąd mielibyśmy wiedzieć, od czego zacząć szukanie?
Wpisy skryptu LoAF mogą Ci pomóc:
scripts: [{
name: 'script',
invoker: 'SPAN.onanimationiteration',
invokerType: 'event-listener',
startTime: 2065,
executionStart: 2065,
duration: 788,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: 'cryptodaphneCoinHandler',
sourceCharPosition: 1831
}]
Chociaż ta funkcja nie miała nic wspólnego z interakcją, spowolniła klatkę animacji, dlatego jest uwzględniona w danych LoAF połączonych ze zdarzeniem interakcji.
Dzięki temu możemy zobaczyć, jak została wywołana funkcja, która opóźniła przetwarzanie interakcji (przez animationiteration detektora), która funkcja była za to odpowiedzialna i gdzie znajdowała się w naszych plikach źródłowych.
8. Opóźnienie prezentacji: gdy aktualizacja nie jest renderowana
Opóźnienie prezentacji to czas od zakończenia działania detektorów zdarzeń do momentu, gdy przeglądarka może narysować nową klatkę na ekranie, pokazując użytkownikowi widoczne informacje zwrotne.
Odśwież stronę, aby ponownie zresetować wartość INP, a następnie otwórz menu. Podczas otwierania występuje wyraźne zacięcie.
Jak to wygląda?
{
name: 'INP',
value: 376,
rating: 'needs-improvement',
delta: 352,
attribution: {
interactionTarget: '#sidenav-button>svg',
interactionType: 'pointer',
inputDelay: 12.8,
processingDuration: 14.7,
presentationDelay: 348.5,
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 651,
duration: 365,
renderStart: 673.2,
styleAndLayoutStart: 1004.3,
firstUIEventTimestamp: 138.6,
blockingDuration: 315,
scripts: [{...}]
}]
}
}
Tym razem większość czasu powolnej interakcji stanowi opóźnienie prezentacji. Oznacza to, że wszystko, co blokuje wątek główny, występuje po zakończeniu działania detektorów zdarzeń.
scripts: [{
entryType: 'script',
invoker: 'FrameRequestCallback',
invokerType: 'user-callback',
startTime: 673.8,
executionStart: 673.8,
duration: 330,
sourceURL: 'http://localhost:8080/js/side-nav.js',
sourceFunctionName: '',
sourceCharPosition: 1193,
}]
Patrząc na pojedynczy wpis w tablicy scripts, widzimy, że czas jest spędzany w funkcji user-callback z funkcji FrameRequestCallback. Tym razem opóźnienie prezentacji jest spowodowane oddzwonieniem requestAnimationFrame.
9. Podsumowanie
Zbieranie danych z pola
Warto zauważyć, że jest to łatwiejsze w przypadku pojedynczego wpisu atrybucji INP z jednego wczytania strony. Jak można agregować te dane, aby debugować INP na podstawie danych z terenu? Ilość przydatnych szczegółów w rzeczywistości utrudnia to zadanie.
Na przykład bardzo przydatne jest wiedzieć, który element strony jest częstym źródłem powolnych interakcji. Jeśli jednak na stronie znajdują się skompilowane nazwy klas CSS, które zmieniają się w zależności od kompilacji, web-vitals selektory z tego samego elementu mogą się różnić w różnych kompilacjach.
Zamiast tego musisz zastanowić się nad konkretną aplikacją, aby określić, co jest najbardziej przydatne i jak można agregować dane. Na przykład przed odesłaniem danych atrybucji za pomocą sygnału możesz zastąpić selektor web-vitals własnym identyfikatorem na podstawie komponentu, w którym znajduje się element docelowy, lub ról ARIA, które spełnia element docelowy.
Podobnie wpisy scripts mogą zawierać w ścieżkach sourceURL hasze oparte na plikach, co utrudnia ich łączenie. Możesz jednak usunąć hasze na podstawie znanego procesu kompilacji przed wysłaniem danych z powrotem.
W przypadku tak złożonych danych nie ma łatwego rozwiązania, ale nawet użycie ich podzbioru jest bardziej wartościowe w procesie debugowania niż brak danych atrybucji.
Uznanie autorstwa wszędzie!
Atrybucja INP oparta na LoAF to przydatne narzędzie do debugowania. Zawiera szczegółowe dane o tym, co dokładnie wydarzyło się podczas interakcji z kolejnym elementem. W wielu przypadkach może wskazać dokładne miejsce w skrypcie, w którym należy rozpocząć optymalizację.
Możesz już używać danych atrybucji INP w dowolnej witrynie.
Nawet jeśli nie masz uprawnień do edytowania strony, możesz odtworzyć proces z tego samouczka, uruchamiając w konsoli Narzędzi deweloperskich ten fragment kodu, aby sprawdzić, co możesz znaleźć:
const script = document.createElement('script');
script.src = 'https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.iife.js';
script.onload = function () {
webVitals.onINP(console.log, {reportAllChanges: true});
};
document.head.appendChild(script);