1. Wprowadzenie
To interaktywne ćwiczenia w programie, dzięki którym dowiesz się, jak mierzyć interakcje z następnym wyrenderowaniem (INP) za pomocą biblioteki web-vitals
.
Wymagania wstępne
- Wiedza z zakresu programowania w językach HTML i JavaScript
- Zalecane: zapoznaj się z dokumentacją wskaźników INP web.dev.
Czego się nauczysz
- Jak dodać do strony bibliotekę
web-vitals
i korzystać z jej danych atrybucji. - Wykorzystaj dane o atrybucji, aby zdiagnozować, gdzie i jak zacząć ulepszać INP.
Co będzie potrzebne
- Komputer z możliwością kopiowania kodu z GitHuba i uruchamiania poleceń npm.
- Edytor tekstu.
- Najnowsza wersja Chrome umożliwiająca pomiar wszystkich interakcji.
2. Konfiguracja
Pobierz i uruchom kod
Kod znajduje się w repozytorium web-vitals-codelabs
.
- Skopiuj repozytorium do terminala:
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 stronę http://localhost:8080/.
Wypróbuj stronę
W ramach tych ćwiczeń w programie wykorzystano Gastropodicon (popularną witrynę z informacjami o anatomii ślimaków), aby zbadać potencjalne problemy z INP.
Spróbuj wejść w interakcję ze stroną, by przekonać się, które interakcje są powolne.
3. Otwieranie Narzędzi deweloperskich w Chrome
Otwórz Narzędzia deweloperskie w sekcji Więcej narzędzi > menu Narzędzia dla deweloperów, klikając stronę prawym przyciskiem myszy i wybierając Zbadaj lub użyj skrótu klawiszowego.
W tym ćwiczeniu w programowaniu użyjemy zarówno panelu Wydajność, jak i Konsoli. W każdej chwili możesz się między nimi przełączać na kartach u góry Narzędzi deweloperskich.
- Problemy z INP występują najczęściej na urządzeniach mobilnych, więc przełącz się na emulację wyświetlacza mobilnego.
- Jeśli testujesz aplikację na komputerze lub laptopie, wydajność będzie prawdopodobnie znacznie lepsza niż na prawdziwym urządzeniu mobilnym. Aby zobaczyć bardziej realistyczne dane, naciśnij ikonę koła zębatego w prawym górnym rogu panelu Wydajność i wybierz 4-krotne spowolnienie procesora.
4. Instaluję web-vitals
web-vitals
to biblioteka JavaScriptu do pomiaru danych o wskaźnikach internetowych, z których korzystają użytkownicy. Możesz użyć biblioteki, aby przechwytywać te wartości, a następnie przesyłać je do punktu końcowego Analytics do późniejszej analizy, aby ustalić, kiedy i gdzie występują powolne interakcje.
Bibliotekę do strony można dodać na kilka różnych sposobów. Sposób instalacji biblioteki w witrynie zależy od sposobu zarządzania zależnościami, procesu kompilacji i innych czynników. Wszystkie opcje znajdziesz w dokumentacji biblioteki.
To ćwiczenie w Codelabs zostanie zainstalowane z npm i bezpośrednio wczyta skrypt, co pozwoli uniknąć zagłębiania się w konkretny proces kompilacji.
Istnieją 2 wersje obiektu web-vitals
, których możesz użyć:
- „Standardowy” musisz używać kompilacji, jeśli chcesz śledzić wartości podstawowych wskaźników internetowych podczas wczytywania strony.
- „Atrybucja” dodaje do każdego wskaźnika dodatkowe informacje na potrzeby debugowania, aby można było zdiagnozować wartość danych.
Do pomiaru INP w tym ćwiczeniach z programowania potrzebna jest kompilacja atrybucji.
Dodaj web-vitals
do zasobu devDependencies
projektu, uruchamiając npm install -D web-vitals
Dodaj web-vitals
do strony:
Dodaj wersję atrybucji skryptu na dole sekcji 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
Przy otwartej konsoli spróbuj jeszcze raz wejść w interakcję ze stroną. Gdy klikasz dookoła strony, nic nie jest rejestrowane.
Wartość INP jest mierzona przez cały cykl życia strony, więc domyślnie web-vitals
nie zgłasza INP, dopóki użytkownik nie opuści lub nie zamknie strony. Jest to idealne rozwiązanie w przypadku zastosowań typu beaconing na przykład w przypadku statystyk, ale nie do interaktywnego debugowania.
web-vitals
udostępnia opcję reportAllChanges
, która zapewnia bardziej szczegółowe raporty. Po włączeniu tej opcji raportowana jest nie każda interakcja, ale każda interakcja jest wolniejsza niż poprzednie.
Spróbuj dodać tę 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żenie strony i interakcje powinny być teraz raportowane w konsoli. Aktualizujemy je za każdym razem, gdy pojawi się nowa najwolniejsza strona. Możesz na przykład wpisać tekst w polu wyszukiwania, a potem usunąć dane wejściowe.
5. Co oznacza atrybucja?
Zaczniemy od pierwszej interakcji ze stroną – od okna z prośbą o zgodę na stosowanie plików cookie.
Wiele stron zawiera skrypty wymagające synchronicznego wywoływania plików cookie po zaakceptowaniu ich przez użytkownika, przez co kliknięcie przebiega wolniej. Tak się dzieje w tym przypadku.
Kliknij Tak, aby zaakceptować (demograficzne) pliki cookie, i przejrzyj dane INP zarejestrowane teraz w konsoli Narzędzi deweloperskich.
Te najważniejsze informacje są dostępne zarówno w ramach standardowych funkcji Web Vitals, jak i w przypadku atrybucji w ramach:
{
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 – „wymagana poprawa” INP. Tablica entries
zawiera wszystkie wartości PerformanceEntry
powiązane z tą interakcją – w tym przypadku tylko jedno zdarzenie kliknięcia.
Aby jednak dowiedzieć się, co dzieje się w tym czasie, najbardziej interesuje nas usługa attribution
. Aby utworzyć dane atrybucji, web-vitals
ustala, która długie klatki animacji (LoAF) pokrywa się ze zdarzeniem kliknięcia. LoAF może następnie udostępnić szczegółowe dane o czasie spędzonym w tej klatce – od uruchomionych skryptów po czas spędzony w wywołaniu zwrotnym, stylu i układzie requestAnimationFrame
.
Aby zobaczyć więcej informacji, rozwiń właściwość attribution
. Dane są znacznie bogatsze.
attribution: {
interactionTargetElement: Element,
interactionTarget: '#confirm',
interactionType: 'pointer',
inputDelay: 27,
processingDuration: 295.6,
presentationDelay: 21.4,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
Najpierw są informacje o tym, z czym nawiązano interakcję:
interactionTargetElement
: odwołanie do aktywnego elementu, z którym nastąpiła interakcja (jeśli nie został on usunięty z DOM).interactionTarget
: selektor do znalezienia elementu na stronie.
Następnie mamy ogólny podział czasu:
inputDelay
: czas od momentu rozpoczęcia interakcji przez użytkownika (np. kliknięcia myszki) do uruchomienia detektora tej interakcji. W tym przypadku opóźnienie sygnału wejściowego wyniesieło około 27 milisekund, nawet przy włączonym ograniczaniu wykorzystania procesora.processingDuration
: czas do zakończenia działania detektorów zdarzeń. Strony często mają wielu detektorów pojedynczego zdarzenia (np.pointerdown
,pointerup
iclick
). Jeśli wszystkie elementy znajdują się w tej samej klatce animacji, zostaną połączone w ten czas. W tym przypadku czas przetwarzania trwa 295,6 milisekunda, czyli większość czasu INP.presentationDelay
: czas od zakończenia działań detektorów zdarzeń do momentu, w którym przeglądarka zakończy malowanie następnej klatki. W tym przypadku będzie to 21, 4 milisekundy.
Fazy INP mogą być niezbędnym sygnałem do diagnozowania potrzeb optymalizacji. Więcej informacji na ten temat znajdziesz w przewodniku po optymalizacji INP.
Co więcej, tabela processedEventEntries
zawiera pięć zdarzeń, a nie tylko pojedyncze zdarzenia z tablicy INP entries
najwyższego poziomu. 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', ...},
],
Wpis najwyższego poziomu to zdarzenie INP, w tym przypadku kliknięcie. Atrybucja processedEventEntries
to wszystkie zdarzenia, które zostały przetworzone w ramach tej samej klatki. Zwróć uwagę, że obejmuje on też inne zdarzenia, np. mouseover
i mousedown
, a nie tylko zdarzenie kliknięcia. Wiedza o tych innych zdarzeniach może być ważna, jeśli również były powolne, ponieważ wszystkie przyczyniły się do wolnego reagowania.
Ostatnia klasa to tablica longAnimationFrameEntries
. To może być pojedynczy wpis, ale w niektórych przypadkach interakcja może obejmować wiele klatek. Oto najprostszy przypadek z pojedynczą 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: [{...}]
}],
Jest tu kilka przydatnych wartości, np. informacji o ilości czasu poświęcanego na określanie stylu. Więcej informacji o tych właściwościach znajdziesz w artykule o interfejsie Long Animation Frames API. Obecnie interesuje nas przede wszystkim właściwość scripts
, która zawiera wpisy dostarczające szczegółowe informacje o skryptach odpowiedzialnych za długotrwałą ramkę:
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żna stwierdzić, że czas został wykorzystany głównie w jednym obiekcie event-listener
wywołanym BUTTON#confirm.onclick
. Widzimy nawet adres URL źródła skryptu i pozycję znaku w miejscu, w którym funkcja została zdefiniowana.
Na wynos
Co można ustalić w tym przypadku na podstawie tych danych atrybucji?
- Interakcja została wywołane przez kliknięcie elementu
button#confirm
(zattribution.interactionTarget
i właściwościinvoker
w wpisie o atrybucji skryptu). - Czas poświęcony głównie na wykonywanie detektorów zdarzeń (od
attribution.processingDuration
w porównaniu z łącznym wskaźnikiemvalue
). - Kod detektora powolnych zdarzeń zaczyna się od odbiornika kliknięć zdefiniowanego w zasadzie
third-party/cmp.js
(zscripts.sourceURL
).
To wystarczająco dużo danych, by określić obszary wymagające optymalizacji.
6. Wiele detektorów zdarzeń
Odśwież stronę, aby konsola Narzędzi deweloperskich była przejrzysta, a interakcja z prośbą o zgodę na stosowanie plików cookie nie była już najdłuższą interakcją.
Zacznij pisać w polu wyszukiwania. Co zawierają dane o atrybucji? Jak myślisz, co się dzieje?
Dane atrybucji
Najpierw wyświetl ogólny skan jednego 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: [...],
}
}
To niska wartość INP (z włączonym ograniczaniem wykorzystania procesora) wynikającą z interakcji klawiatury z elementem input#search-terms
. Przez większość czasu – 1061 milisekund z łącznej liczby 1072 milisekund – INP – był poświęcany czas przetwarzania.
Wpisy w języku scripts
są jednak ciekawsze.
Rzutowanie układu
Pierwszy wpis tablicy scripts
zapewnia nam cenny kontekst:
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 zachodzi podczas wykonywania tego skryptu, który jest odbiornikiem input
(wywołującym jest INPUT#search-terms.oninput
). Podana jest nazwa funkcji (handleSearch
) oraz pozycja znaku w pliku źródłowym index.js
.
Dostępna jest jednak nowa właściwość: forcedStyleAndLayoutDuration
. Był to czas spędzony w trakcie wywoływania tego skryptu, podczas którego przeglądarka została zmuszona do przekazania strony. Innymi słowy, 78% czasu – 388 milisekund z 497 – poświęcane na wykonanie tego detektora zdarzeń było poświęcane na thrashowanie układu.
Ten problem powinien być priorytetowo rozwiązany.
Słuchacze powracający
Same kolejne 2 kolejne pozycje skryptu nie są szczególnie istotne:
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
}]
Obie wpisy to detektory keyup
, które są wykonywane jeden po drugim. Detektory to funkcje anonimowe (dlatego właściwość sourceFunctionName
nie zawiera żadnych danych), ale nadal mamy plik źródłowy i pozycję znaku, więc możemy ustalić, gdzie znajduje się kod.
Dziwne jest, że oba adresy pochodzą z tego samego pliku źródłowego i tej samej pozycji znaku.
W rezultacie przeglądarka przetworzyła wiele naciśnięć klawiszy w ramach jednej klatki animacji, przez co detektor zdarzeń uruchomił się dwukrotnie, zanim zdołano cokolwiek wyrenderować.
Ten efekt może się również nasilać – im dłużej detektory zdarzeń minęły, tym więcej mogą przybyć dodatkowych zdarzeń wejściowych, co znacznie wydłuża powolną interakcję.
Ponieważ jest to interakcja z wyszukiwaniem lub autouzupełnianiem, dobrym rozwiązaniem będzie osłabienie danych wejściowych, tak by na klatkę można było przetworzyć co najwyżej 1 naciśnięcie klawisza.
7. Opóźnienie wejściowe
Typowym powodem opóźnień danych wejściowych – czas od interakcji użytkownika do momentu, w którym detektor zdarzeń może rozpocząć przetwarzanie interakcji – jest to, że wątek główny jest zajęty. Może to mieć kilka przyczyn:
- Strona się wczytuje, a wątek główny jest zajęty konfigurowaniem DOM, układem i stylem strony oraz ocenianiem i uruchamianiem skryptów.
- Strona jest przeważnie zajęta – na przykład trwają obliczenia, animacje oparte na skryptach lub reklamy.
- Przetwarzanie poprzednich interakcji trwa tak długo, że opóźniają przyszłe interakcje, co zaobserwowaliśmy w ostatnim przykładzie.
Na stronie demonstracyjnej znajduje się tajna funkcja: gdy klikniesz logo ślimaka u góry strony, rozpocznie się animowanie i przeprowadzenie intensywnej pracy w języku JavaScript.
- Kliknij logo ślimaka, aby rozpocząć animację.
- Zadania JavaScript są wyzwalane, gdy ślimak znajduje się na dole strony odsyłającej. Postaraj się wejść w interakcję ze stroną jak najbliżej dołu odrzuceń i sprawdź, jak wysoki wskaźnik INP możesz wywołać.
Na przykład nawet jeśli nie aktywujesz żadnych innych detektorów zdarzeń, takich jak kliknięcie i skupienie się na polu wyszukiwania w chwili, gdy ślimak wyskakiwał, działanie wątku głównego spowoduje, że strona przestanie odpowiadać przez zauważalny czas.
Na wielu stronach skomplikowana praca w wątku głównym nie jest tak dobra, ale to dobry przykład tego, jak można ją zidentyfikować w danych atrybucji INP.
Oto przykład atrybucji polegającej na skupieniu się tylko na polu wyszukiwania podczas skakania ś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ń były wykonywane szybko – czas przetwarzania wynosił 4,9 milisekunda, a większość słabych interakcji była poświęcana w opóźnieniu danych wejściowych, co daje 702,3 milisekunda z 728.
Taka sytuacja może być trudna do debugowania. Mimo że wiemy, z czym i w jaki sposób użytkownik wchodził w interakcję, wiemy też, że ta część interakcji zakończyła się szybko i nie była problemem. Zamiast tego pojawiło się na stronie coś innego, co opóźniło interakcję z rozpoczęciem przetwarzania, ale skąd mamy wiedzieć, od czego zacząć szukać?
Wpisy skryptu LoAF mogą pomóc Ci w tej kwestii:
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
}]
Mimo że ta funkcja nie miała nic wspólnego z interakcją, spowolniła klatkę animacji i dlatego jest uwzględniana w danych LoAF, które są połączone ze zdarzeniem interakcji.
Widać tutaj, jak została uruchomiona funkcja opóźniania przetwarzania interakcji (przez detektor animationiteration
), która funkcja była za to odpowiedzialna i gdzie znajduje się w plikach źródłowych.
8. Opóźnienie prezentacji: gdy aktualizacja nie zostanie wyrenderowana
Opóźnienie prezentacji mierzy czas od zakończenia działania detektorów zdarzeń do momentu, gdy przeglądarka jest w stanie wyrenderować nową klatkę na ekranie i wyświetlić użytkownikowi informację zwrotną.
Odśwież stronę, aby ponownie zresetować wartość INP, a następnie otwórz menu z 3 kreskami. Po otwarciu występują jakieś problemy.
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 to opóźnienie prezentacji stanowi większość powolnej interakcji. Oznacza to, że to, co blokuje wątek główny, następuje po zakończeniu 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,
}]
Gdy patrzymy na pojedynczy wpis w tablicy scripts
, widzimy czas spędzony w tabeli user-callback
w tabeli FrameRequestCallback
. Tym razem opóźnienie prezentacji jest spowodowane wywołaniem zwrotnym requestAnimationFrame
.
9. Podsumowanie
Agregowanie danych pól
Łatwo to zrobić, analizując pojedynczy wpis atrybucji INP podczas wczytywania strony. W jaki sposób można agregować te dane na potrzeby debugowania INP na podstawie danych z pól? Ilość pomocnych szczegółów tak naprawdę utrudnia ten proces.
Bardzo dobrze jest na przykład dowiedzieć się, który element strony jest częstym źródłem powolnych interakcji. Jeśli jednak na stronie są skompilowane nazwy klas CSS, które zmieniają się z kompilacji, selektory web-vitals
tego samego elementu mogą się różnić w zależności od kompilacji.
Zamiast tego trzeba wymyślić konkretną aplikację, aby określić, co jest najbardziej przydatne i w jaki sposób można agregować dane. Na przykład przed zwróceniem danych atrybucji typu beacon możesz zastąpić selektor web-vitals
własnym identyfikatorem utworzonym na podstawie komponentu, w którym znajduje się cel, lub ról ARIA realizowanych przez ten cel.
Podobnie wpisy scripts
mogą mieć w ścieżkach sourceURL
oparte na plikach hasze, co utrudnia ich łączenie. Możesz jednak przed wysłaniem danych z powrotem usunąć te hasze zgodnie ze znanym procesem kompilacji.
Niestety nie ma łatwej ścieżki z danymi, ale nawet wykorzystanie ich podzbioru jest cenniejsze niż żadne dane atrybucji w procesie debugowania.
Atrybucja w każdym miejscu
Atrybucja INP oparta na LoAF to zaawansowane narzędzie ułatwiające debugowanie. Udostępnia szczegółowe dane na temat tego, co dokładnie wydarzyło się podczas INP. W wielu przypadkach może wskazać dokładne miejsce w skrypcie, od którego należy rozpocząć działania optymalizacyjne.
Teraz możesz używać danych atrybucji INP w dowolnej witrynie.
Nawet jeśli nie masz uprawnień do edytowania strony, możesz odtworzyć ten proces z tego ćwiczenia, uruchamiając w konsoli Narzędzi deweloperskich ten fragment kodu:
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);