Pomiar interakcji do kolejnego wyrenderowania (INP)

1. Wprowadzenie

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

Wymagania wstępne

Czego się nauczysz

  • Jak dodać bibliotekę web-vitals do strony i korzystać z jej danych o atrybucji.
  • Użyj danych atrybucji, aby określić, 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,
  • mieć 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.

  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 stronę 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 deweloperów, klikając stronę prawym przyciskiem myszy i wybierając Zbadaj lub używając skrótu klawiszowego.

W tym laboratorium będziemy korzystać zarówno z panelu Wydajność, jak i z konsoli. W każdej chwili możesz przełączać się między nimi, 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 Spowolnienie procesora 4x.

Zrzut ekranu panelu Wydajność w Narzędziach deweloperskich obok aplikacji z wybranym 4-krotnym spowolnieniem procesora

4. Instalowanie biblioteki web-vitals

web-vitals to biblioteka JavaScriptu służąca do pomiaru wskaźników Web Vitals, które są istotne dla Twoich użytkowników. Możesz użyć tej biblioteki do rejestrowania 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ą powolne 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 wskaźników Core Web Vitals podczas wczytywania strony.
  • Wersja „atrybucja” dodaje do każdego rodzaju danych dodatkowe informacje do debugowania, które pomagają ustalić, 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 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

Spróbuj ponownie wejść w interakcję ze stroną, gdy konsola jest otwarta. Podczas klikania różnych elementów na stronie nic nie jest rejestrowane.

INP jest mierzony 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 statystyki, ale mniej przydatne w przypadku interaktywnego debugowania.

web-vitals udostępnia opcję reportAllChanges, która umożliwia generowanie bardziej szczegółowych raportów. 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 raportowane do konsoli i aktualizowane za każdym razem, gdy pojawi się nowa najwolniejsza interakcja. Na przykład wpisz coś w polu wyszukiwania, a następnie usuń wpisane słowa.

Zrzut ekranu z konsoli Narzędzi deweloperskich, na którym widać 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 z prośbą o zgodę na stosowanie plików cookie.

Na wielu stronach znajdują się skrypty, które wymagają synchronicznego wywoływania plików cookie, gdy użytkownik je zaakceptuje. 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 zarejestrowany 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 1 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óre długie animacje nakładają się na zdarzenie kliknięcia. 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 zobaczyć 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 weszliśmy w interakcję (jeśli element nie został usunięty z DOM).
  • interactionTarget: selektor do znajdowania elementu na stronie.

Następnie przedstawiamy ogólny harmonogram:

  • inputDelay: czas, jaki minął 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, pointerup i click). 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 elementów, które wymagają optymalizacji. Więcej informacji na ten temat znajdziesz w przewodniku po optymalizacji 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 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ć kluczowe, jeśli również były powolne, ponieważ wszystkie przyczyniły się do powolnej reakcji.

Ostatnia jest 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 przede wszystkim 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 funkcji event-listener wywoływanej 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ć w tej sprawie na podstawie tych danych 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ń (w porównaniu z całkowitą wartością value).attribution.processingDuration
  • 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. Co pokazują dane atrybucji? Jak myślisz, co się może dziać?

Dane atrybucji

Najpierw ogólne omówienie 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: [...],
  }
}

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.

Przesunięcia 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 detektorem input (wywołującym jest INPUT#search-terms.oninput). Podana jest nazwa funkcji (handleSearch), a także pozycja znaku w pliku źródłowym index.js.

Jest jednak nowa usługa: 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.

Naprawa tego błędu powinna być priorytetem.

Powtarzający się 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 usłudze sourceFunctionName nie są zgłaszane żadne informacje), 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 wyrenderowane.

Ten efekt może się też kumulować: im dłużej trwa obsługa 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, aby w każdej klatce przetwarzane było co najwyżej jedno naciśnięcie klawisza.

7. Opóźnienie wejściowe

Typową przyczyną opóźnień w reakcji na dane wejściowe – 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ę wczytuje, a główny wątek jest zajęty początkową pracą związaną z konfigurowaniem DOM, układem i stylowaniem strony oraz ocenianiem i uruchamianiem skryptów.
  • Strona jest zwykle zajęta, np. przez obliczenia, animacje oparte na skryptach lub reklamy.
  • Przetwarzanie poprzednich interakcji trwa tak długo, że opóźnia przyszłe 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 momentu odrzucenia 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 głównym wątku 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. Chociaż wiemy, z czym i w jaki sposób użytkownik wszedł w interakcję, wiemy też, że ta część interakcji została szybko zakończona 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), która funkcja była za to odpowiedzialna i gdzie znajdowała się w naszych plikach źródłowych.

8. Opóźnienie wyświetlania: gdy aktualizacja nie jest wyświetlana

Opóźnienie prezentacji to czas od zakończenia działania detektorów zdarzeń do momentu, gdy przeglądarka może wyrenderować 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 z 3 kreskami. 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ść opóźnienia interakcji stanowi opóźnienie prezentacji. Oznacza to, że wszystko, co blokuje główny wątek, 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 user-callback z FrameRequestCallback. Tym razem opóźnienie prezentacji jest spowodowane wywołaniem zwrotnym requestAnimationFrame.

9. Podsumowanie

Agregowanie 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 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 poszczególnych 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 wysłaniem danych atrybucji za pomocą sygnałów możesz zastąpić selektor web-vitals własnym identyfikatorem opartym na komponencie, w którym znajduje się element docelowy, lub na rolach ARIA, które spełnia element docelowy.

Podobnie wpisy scripts mogą mieć w ścieżkach sourceURL hasze oparte na plikach, które utrudniają ich łączenie, ale przed wysłaniem danych z powrotem możesz usunąć hasze na podstawie znanego procesu kompilacji.

W przypadku tak złożonych danych nie ma łatwego rozwiązania, ale nawet użycie ich podzbioru jest cenniejsze niż brak danych atrybucji w procesie debugowania.

Atrybucja 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ąć działania optymalizacyjne.

Możesz teraz używać danych atrybucji INP w dowolnej witrynie.

Nawet jeśli nie masz dostępu do edytowania strony, możesz odtworzyć proces z tego laboratorium, 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);

Więcej informacji