Pomiar interakcji do kolejnego wyrenderowania (INP)

1. Wprowadzenie

To interaktywne ćwiczenie, z którego dowiesz się, jak mierzyć czas od interakcji do kolejnego wyrenderowania (INP) za pomocą biblioteki web-vitals.

Wymagania wstępne

Czego się nauczysz

  • Jak dodać bibliotekę web-vitals do 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.

  1. Sklonuj repozytorium w terminalu: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git.
  2. Przejdź do sklonowanego katalogu: cd web-vitals-codelabs/measuring-inp.
  3. Zainstaluj zależności: npm ci.
  4. Uruchom serwer WWW: npm run start
  5. W przeglądarce otwórz adres http://localhost:8080/.

Wypróbuj stronę

W tym ćwiczeniu wykorzystamy Gastropodicon (popularną witrynę referencyjną dotyczącą anatomii ślimaków), aby zbadać potencjalne problemy z INP.

Zrzut ekranu strony demonstracyjnej Gastropodicon

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 ćwiczeniu będziemy korzystać zarówno z panelu Skuteczność, jak i z 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.

Zrzut ekranu panelu Wydajność w Narzędziach dla programistów obok aplikacji z wybranym 4-krotnym spowolnieniem 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 analityki 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 wskaźnika dodatkowe informacje na potrzeby debugowania, które pomagają zdiagnozować, dlaczego dany wskaźnik 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ą, gdy konsola jest otwarta. 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 celów analitycznych, ale mniej idealne 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, która jest 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.

Zrzut ekranu z konsoli Narzędzi dla programistów, na której wyświetlają się komunikaty INP

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.

Obiekt danych INP rejestrowany 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: odwołanie 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, pointerupclick). 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.

W tablicy processedEventEntries znajduje się 5 zdarzeń, a w tablicy INP najwyższego poziomu entries – tylko 1 zdarzenie. 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 mouseovermousedown, 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 powolnego reagowania.

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 większość czasu spędzono w event-listener, wywołanym w BUTTON#confirm.onclick. Możemy nawet zobaczyć adres URL źródła skryptu i pozycję znaku, w której 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 (z attribution.interactionTarget i właściwości invoker w pozycji atrybucji skryptu).
  • Czas był poświęcony głównie na wykonywanie funkcji nasłuchujących zdarzeń (attribution.processingDuration w porównaniu z całkowitą wartością value).
  • Kod detektora powolnych zdarzeń zaczyna się od detektora kliknięć zdefiniowanego w pliku third-party/cmp.js (od scripts.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 sprawić, że interakcja związana ze zgodą na stosowanie plików cookie nie będzie już najdłuższą interakcją.

Zacznij pisać w polu wyszukiwania. Jakie informacje zawierają 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) zajęło przetwarzanie.

Wpisy scripts są jednak ciekawsze.

Szamotanie 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 tego 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 szamotanie 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: