Znajdowanie powolnych interakcji w terenie

Dowiedz się, jak wykryć powolne interakcje w danych pól witryny, aby znaleźć możliwości poprawy interakcji z kolejnym wyrenderowaniem.

Dane pól to dane, dzięki którym dowiesz się, jak użytkownicy korzystają z Twojej witryny. Informuje o problemach, których nie można znaleźć samych w danych laboratoryjnych. Jeśli chodzi o interakcję z kolejnym wyrenderowaniem (INP), dane terenowe są niezbędne do identyfikowania powolnych interakcji i dostarczają istotnych wskazówek, które pomagają rozwiązać te problemy.

Z tego przewodnika dowiesz się, jak szybko ocenić wartość INP witryny, korzystając z danych pól z Raportu na temat użytkowania Chrome (CrUX). Pozwoli Ci to sprawdzić, czy w Twojej witrynie występuje problem z INP. Następnie dowiesz się, jak korzystać z kompilacji atrybucji biblioteki JavaScript Web-vitals i nowych statystyk dostarczanych przez Long Animation Frames API (LoAF) – do gromadzenia i interpretowania danych pól dotyczących powolnych interakcji w witrynie.

Zacznij od raportu CrUX, aby ocenić INP witryny

Jeśli nie zbierasz danych pól od użytkowników witryny, możesz zacząć od raportu CrUX. CrUX zbiera dane pól od prawdziwych użytkowników Chrome, którzy zgodzili się na wysyłanie danych telemetrycznych.

Dane raportu CrUX są widoczne w wielu różnych obszarach i zależą od zakresu poszukiwanych informacji. Raport CrUX dostarcza dane o INP i innych podstawowych wskaźnikach internetowych dotyczące:

  • Poszczególne strony i całe źródła z użyciem PageSpeed Insights.
  • Typy stron. Na przykład wiele witryn e-commerce ma typy „Strona ze szczegółami produktu” i „Strona z listą produktów”. Dane raportu na temat użytkowania Chrome dotyczące unikalnych typów stron możesz uzyskać w Search Console.

Na początek możesz wpisać adres URL swojej witryny w PageSpeed Insights. Gdy wpiszesz adres URL, jego pola (jeśli są dostępne) będą wyświetlać się w przypadku wielu wskaźników, w tym INP. Możesz też użyć przełączników, aby sprawdzić wartości INP w przypadku wymiarów dotyczących urządzeń mobilnych i komputerów.

Dane pól przedstawione przez CrUX w PageSpeed Insights: LCP, INP i CLS w przypadku 3 podstawowych wskaźników internetowych, TTFB i FCP jako dane diagnostyczne oraz FID jako wycofane podstawowe wskaźniki internetowe.
Odczyt danych CrUX zgodnie z statystykami PageSpeed. W tym przykładzie należy poprawić wartość INP danej strony internetowej.

Te dane są przydatne, bo informują o problemach. Raport CrUX nie może jednak określić, co powoduje problemy. Dostępnych jest wiele rozwiązań do monitorowania użytkowników (Real User Monitoring, RUM), które pomogą Ci zbierać dane z własnych pól od użytkowników witryny. Jedną z opcji jest samodzielne zbieranie danych pól za pomocą biblioteki JavaScript Web-vitals.

Zbieraj dane pól za pomocą biblioteki JavaScript web-vitals

Biblioteka JavaScript web-vitals to skrypt, który możesz załadować w swojej witrynie, aby zbierać dane terenowe od jej użytkowników. Pozwala on rejestrować szereg danych, w tym wartość INP w przeglądarkach, które go obsługują.

Obsługa przeglądarek

  • 96
  • 96
  • x
  • x

Źródło

Aby uzyskać podstawowe dane INP od użytkowników w tym polu, można użyć standardowej kompilacji biblioteki Web-vitals:

import {onINP} from 'web-vitals';

onINP(({name, value, rating}) => {
  console.log(name);    // 'INP'
  console.log(value);   // 512
  console.log(rating);  // 'poor'
});

Aby przeanalizować dane pól pochodzące od użytkowników, wyślij je gdzieś:

import {onINP} from 'web-vitals';

onINP(({name, value, rating}) => {
  // Prepare JSON to be sent for collection. Note that
  // you can add anything else you'd want to collect here:
  const body = JSON.stringify({name, value, rating});

  // Use `sendBeacon` to send data to an analytics endpoint.
  // For Google Analytics, see https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics.
  navigator.sendBeacon('/analytics', body);
});

Jednak same dane nie dadzą Ci zbyt wielu informacji niż raport na temat użytkowania Chrome. Właśnie tu do akcji wkracza zasada atrybucji w bibliotece materiałów internetowych.

Osiągnij więcej dzięki modelowi atrybucji w bibliotece Web-vitals

Struktura atrybucji z biblioteki Web-vital zawiera dodatkowe dane, które możesz uzyskać od użytkowników, aby pomóc Ci w rozwiązywaniu problemów z interakcjami wpływającymi na wartość INP Twojej witryny. Do tych danych można uzyskać dostęp za pomocą obiektu attribution pojawiającego się w metodzie onINP() biblioteki:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, rating, attribution}) => {
  console.log(name);         // 'INP'
  console.log(value);        // 512
  console.log(rating);       // 'poor'
  console.dir(attribution);  // Attribution data
});
Sposób wyświetlania dzienników konsoli z biblioteki Web-vitals. Konsola w tym przykładzie pokazuje nazwę danych (INP), wartość INP (56), gdzie mieści się ona w zakresie progów INP (dobra), oraz różne bity informacji widoczne w obiekcie atrybucji, w tym wpisy z interfejsu Long Animation Frame API.
Jak dane z biblioteki Web-vitals wyświetlają się w konsoli.

Oprócz wartości INP strony model atrybucji dostarcza wielu danych, których możesz użyć, aby poznać przyczyny powolnych interakcji oraz dowiedzieć się, na której jej części musisz się skupić. Pomoże Ci on odpowiedzieć na ważne pytania, takie jak:

  • „Czy użytkownik wszedł w interakcję ze stroną podczas jej wczytywania?”
  • „Czy moduły obsługi zdarzeń interakcji działały przez długi czas?”
  • „Czy uruchomienie kodu modułu obsługi zdarzeń interakcji było opóźnione? Jeśli tak, co jeszcze działo się w tym czasie w wątku głównym?”
  • „Czy ta interakcja spowodowała dużo pracy z renderowaniem, która opóźniła namalowanie następnej klatki?”

W tabeli poniżej znajdziesz podstawowe dane o atrybucji, które możesz uzyskać z biblioteki. Dzięki temu możesz łatwiej znaleźć ogólne przyczyny powolnych interakcji w witrynie:

Klucz obiektu attribution Dane
interactionTarget Selektor CSS wskazujący element, który wygenerował wartość INP strony, np. button#save.
interactionType Typ interakcji – kliknięcia, dotknięcia lub naciśnięcia klawiatury.
inputDelay* Opóźnienie wejściowe interakcji.
processingDuration* Czas od rozpoczęcia działania pierwszego detektora zdarzeń w odpowiedzi na interakcję użytkownika do zakończenia przetwarzania całego detektora.
presentationDelay* Opóźnienie prezentacji interakcji, które następuje od momentu zakończenia obsługi modułów obsługi zdarzeń do wyrenderowania następnej klatki.
longAnimationFrameEntries* Wpisy z LoAF powiązane z interakcją. Więcej informacji znajdziesz poniżej.
*Nowość w wersji 4

Począwszy od wersji 4 biblioteki Web-vitals, możesz uzyskać jeszcze bardziej szczegółowy wgląd w problematyczne interakcje na podstawie dostarczanych przez nią danych, takich jak podział faz INP (opóźnienie wejściowe, czas przetwarzania i opóźnienie prezentacji) oraz Long Animation Frame API (LoAF).

Interfejs API Long Animation Frame (LoAF)

Obsługa przeglądarek

  • 123
  • 123
  • x
  • x

Źródło

Debugowanie interakcji z wykorzystaniem danych terenowych to nie lada wyzwanie. Jednak dane z LoAF pozwalają uzyskać lepszy wgląd w przyczyny powolnych interakcji, ponieważ LoAF udostępnia wiele szczegółowych danych czasowych i innych danych, dzięki którym można określić konkretne przyczyny, a co ważniejsze, miejsce problemu w kodzie witryny.

Kompilacja atrybucji biblioteki Web-vitals ujawnia tablicę wpisów LoAF w kluczu longAnimationFrameEntries obiektu attribution. Poniższa tabela przedstawia kilka kluczowych danych, które można znaleźć w każdym wpisie LoAF:

Klucz obiektu wejściowego LoAF Dane
duration Czas trwania długiej klatki animacji, do momentu zakończenia układu, ale z wyłączeniem malowania i komponowania.
blockingDuration Łączny czas w ramce, przez który przeglądarka nie mogła szybko odpowiedzieć z powodu długich zadań. Ten czas blokowania może obejmować długie zadania z uruchomionym JavaScriptem, a także późniejsze długie zadania renderowania w ramce.
firstUIEventTimestamp Sygnatura czasowa zdarzenia w kolejce w ramach klatki. Ta opcja przydaje się do określania początku opóźnienia wejściowego interakcji.
startTime Sygnatura czasowa rozpoczęcia klatki.
renderStart Rozpoczęcie renderowania ramki. Obejmuje to wszystkie wywołania zwrotne requestAnimationFrame (i ewentualne wywołania zwrotne ResizeObserver), ale potencjalnie przed rozpoczęciem pracy nad stylem/układem.
styleAndLayoutStart Kiedy styl/układ jest widoczny w kadrze. Ta opcja może być pomocna przy określaniu długości prac nad stylem lub układem podczas wyszukiwania innych dostępnych sygnatur czasowych.
scripts Tablica elementów z informacjami o skrypcie wpływającymi na wartość INP strony.
Wizualizacja długiej klatki animacji według modelu LoAF.
Diagram kodów czasowych długiej klatki animacji zgodnie z interfejsem LoAF API (minus blockingDuration).

Wszystkie te informacje wiele mówią o tym, co spowalnia interakcję. Szczególnie interesujące może być jednak tablica scripts pojawiająca się w wpisach LoAF:

Klucz obiektu atrybucji skryptu Dane
invoker Wywołujący. Może się ona różnić w zależności od typu wywołania opisanego w następnym wierszu. Przykładami wywołań mogą być takie wartości jak 'IMG#id.onload', 'Window.requestAnimationFrame' i 'Response.json.then'.
invokerType Typ wywołującego. Możliwe wartości: 'user-callback', 'event-listener', 'resolve-promise', 'reject-promise', 'classic-script' lub 'module-script'.
sourceURL Adres URL skryptu, z którego pochodzi długa klatka animacji.
sourceCharPosition Pozycja znaku w skrypcie określona przez atrybut sourceURL.
sourceFunctionName Nazwa funkcji we zidentyfikowanym skrypcie.

Każdy wpis w tej tablicy zawiera dane widoczne w tabeli, które zapewniają informacje o skrypcie odpowiedzialnym za powolne interakcje oraz o jego skutkach.

Mierzenie i identyfikowanie najczęstszych przyczyn powolnych interakcji

Aby zorientować się, w jaki sposób można wykorzystać te informacje, w tym przewodniku objaśniamy teraz, jak korzystać z danych LoAF widocznych w bibliotece web-vitals do określania niektórych przyczyn powolnych interakcji.

Długie czasy przetwarzania

Czas przetwarzania interakcji to czas potrzebny na ukończenie wywołań zwrotnych modułu obsługi zarejestrowanych zdarzeń interakcji oraz wszystkie inne zdarzenia, które mogą się wydarzyć między nimi. Długie czasy przetwarzania są wskazane w bibliotece narzędzi internetowych:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {processingDuration} = attribution; // 512.5
});

To naturalne, że główną przyczyną powolnej interakcji jest zbyt długie uruchamianie kodu modułu obsługi zdarzeń, ale nie zawsze tak jest. Gdy upewnisz się, że na tym polega problem, możesz dokładniej przeanalizować dane LoAF:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {processingDuration} = attribution; // 512.5

  // Get the longest script from LoAF covering `processingDuration`:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.sort((a, b) => b.duration - a.duration)[0];

  if (script) {
    // Get attribution for the long-running event handler:
    const {invokerType} = script;        // 'event-listener'
    const {invoker} = script;            // 'BUTTON#update.onclick'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

Jak widać w poprzednim fragmencie kodu, możesz wykorzystać dane LoAF, aby znaleźć dokładną przyczynę interakcji z wartościami o długim czasie przetwarzania, takimi jak:

  • Element i jego zarejestrowany detektor zdarzeń.
  • Plik skryptu i pozycja w nim znaku zawiera długo trwający kod modułu obsługi zdarzeń.
  • Nazwa funkcji.

Ten typ danych jest nieoceniony. Nie musisz już sprawdzać, która interakcja – lub które z jej modułów obsługi zdarzeń – odpowiadała za długie wartości czasu przetwarzania. Poza tym skrypty innych firm mogą często rejestrować własne moduły obsługi zdarzeń, więc możesz określić, czy to nie Twój kod jest odpowiedzialny za to zadanie. W przypadku kodu, nad którym masz kontrolę, warto zoptymalizować długie zadania.

Duże opóźnienia wprowadzania danych

Długotrwałe moduły obsługi zdarzeń są powszechne, ale istnieją też inne części interakcji, które warto wziąć pod uwagę. Jedna część odbywa się przed czasem przetwarzania, który jest nazywany opóźnieniem danych wejściowych. Jest to czas od zainicjowania interakcji przez użytkownika do momentu rozpoczęcia wykonywania wywołań zwrotnych modułu obsługi zdarzeń i tego, gdy wątek główny przetwarza już inne zadanie. Kompilacja atrybucji w bibliotece Web-vitals może informować o długości opóźnienia wprowadzania danych interakcji:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536
});

Jeśli zauważysz, że w przypadku niektórych interakcji występują duże opóźnienia wprowadzania danych, musisz dowiedzieć się, co działo się na stronie w momencie interakcji, co spowodowało długie opóźnienie danych wejściowych. Często sprowadza się to do tego, czy interakcja miała miejsce podczas wczytywania strony, czy już po jej zakończeniu.

Czy wystąpiło podczas wczytywania strony?

Podczas ładowania strony wątek główny jest często najbardziej obciążony. W tym czasie wszelkie zadania są umieszczane w kolejce i przetwarzane, a jeśli użytkownik próbuje w tym czasie korzystać ze strony, może to opóźnić działanie. W przypadku stron, które wczytują dużo kodu JavaScript, mogą się rozpocząć pracę nad kompilowaniem i oceną skryptów oraz wykonywanie funkcji przygotowujących stronę do interakcji z użytkownikiem. Może to przeszkodzić w interakcji użytkownika w trakcie tej aktywności. Możesz sprawdzić, czy tak jest w przypadku użytkowników Twojej witryny:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536

  // Get the longest script from the first LoAF entry:
  const loaf = attribution.longAnimationFrameEntries[0];
  const script = loaf?.scripts.sort((a, b) => b.duration - a.duration)[0];

  if (script) {
    // Invoker types can describe if script eval blocked the main thread:
    const {invokerType} = script;    // 'classic-script' | 'module-script'
    const {sourceLocation} = script; // 'https://example.com/app.js'
  }
});

Jeśli po zarejestrowaniu tych danych zauważysz duże opóźnienia wejściowe i typów wywołujących 'classic-script' lub 'module-script', można spokojnie stwierdzić, że skrypty w Twojej witrynie sprawdzają się bardzo długo i blokują wątek główny na tyle długo, aby opóźniać interakcje. Możesz skrócić ten czas przez podzielenie skryptów na mniejsze pakiety, odłożenie wczytywania początkowo nieużywanego kodu w późniejszym czasie oraz sprawdzenie witryny pod kątem nieużywanego kodu, który możesz całkowicie usunąć.

Czy wystąpił po wczytaniu strony?

Chociaż opóźnienia danych często występują podczas wczytywania strony, zawsze mogą się pojawić po wczytaniu strony z zupełnie innej przyczyny. Częstym powodem opóźnień danych wejściowych po wczytaniu strony może być kod uruchamiany okresowo z powodu wcześniejszego wywołania setInterval, a nawet wywołania zwrotne zdarzenia, które zostały umieszczone w kolejce i wciąż są przetwarzane.

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536

  // Get the longest script from the first LoAF entry:
  const loaf = attribution.longAnimationFrameEntries[0];
  const script = loaf?.scripts.sort((a, b) => b.duration - a.duration)[0];

  if (script) {
    const {invokerType} = script;        // 'user-callback'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

Tak jak w przypadku rozwiązywania problemów z dużymi wartościami czasu przetwarzania, duże opóźnienia danych wejściowych z powodu wspomnianych wcześniej przyczyn zapewnią Ci szczegółowe dane o atrybucji skryptu. Jedyna różnica polega na tym, że typ wywołania zmieni się w zależności od charakteru prac, które opóźniają interakcję:

  • 'user-callback' oznacza, że zadanie blokujące pochodziło z setInterval, setTimeout lub nawet requestAnimationFrame.
  • 'event-listener' oznacza, że zadanie blokujące pochodziło z wcześniejszych danych wejściowych, które zostały umieszczone w kolejce i nadal są przetwarzane.
  • Wartości 'resolve-promise' i 'reject-promise' oznaczają, że zadanie blokujące pochodziło z działania asynchronicznego, które zostało rozpoczęte wcześniej i zostało ukończone lub odrzucone w momencie, gdy użytkownik próbował wejść w interakcję ze stroną, opóźniając interakcję.

W każdym przypadku dane o atrybucji skryptu pozwolą Ci zorientować się, gdzie zacząć szukać i czy opóźnienie danych wejściowych jest spowodowane przez Twój kod, czy przez skrypt zewnętrzny.

Długie opóźnienia prezentacji

Opóźnienia prezentacji to ostatni etap interakcji i rozpoczynają się po zakończeniu modułów obsługi zdarzenia interakcji aż do momentu wyrenderowania następnej klatki. Pojawiają się, gdy w wyniku interakcji działanie modułu obsługi zdarzeń zmienia wizualny stan interfejsu użytkownika. Podobnie jak w przypadku czasu przetwarzania i opóźnień wejściowych, biblioteka Web-vitals może podać czas opóźnienia prezentacji dla danej interakcji:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 113.32307691
});

Jeśli po zarejestrowaniu tych danych zauważysz duże opóźnienia w prezentacji interakcji wpływających na wartość INP Twojej witryny, przyczyny mogą być różne. Oto kilka powodów, na które warto zwrócić uwagę.

Drogie zmiany stylu i układu

Długie opóźnienia w prezentacji mogą być kosztowne podczas przeliczania stylów i układów, które mogą wynikać z wielu przyczyn, takich jak złożone selektory arkusza CSS czy duże rozmiary DOM. Możesz mierzyć czas trwania tej pracy za pomocą kodów czasowych LoAF dostępnych w bibliotece Web-vitals:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 113.32307691

  // Get the longest script from the last LoAF entry:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.sort((a, b) => b.duration - a.duration)[0];

  // Get necessary timings:
  const {startTime} = loaf; // 2120.5
  const {duration} = loaf;  // 1002

  // Figure out the ending timestamp of the frame (approximate):
  const endTime = startTime + duration; // 3122.5

  // Get the start timestamp of the frame's style/layout work:
  const {styleAndLayoutStart} = loaf; // 3011.17692309

  // Calculate the total style/layout duration:
  const styleLayoutDuration = endTime - styleAndLayoutStart; // 111.32307691

  if (script) {
    // Get attribution for the event handler that triggered
    // the long-running style and layout operation:
    const {invokerType} = script;        // 'event-listener'
    const {invoker} = script;            // 'BUTTON#update.onclick'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

LoAF nie podpowie Ci, jak długo trwa praca nad stylem i układem, ale pokaże, kiedy rozpoczął się. Dzięki tej sygnaturze czasowej rozpoczęcia możesz użyć innych danych z LoAF, aby obliczyć dokładny czas trwania tej pracy, określając czas zakończenia klatki i odejmując od tej sygnatury czasowej datę rozpoczęcia działania stylu i układu.

Długo trwające wywołania zwrotne: requestAnimationFrame

Jedną z możliwych przyczyn długich opóźnień w prezentacji jest zbyt duża ilość pracy wykonywanej podczas wywołania zwrotnego requestAnimationFrame. Zawartość tego wywołania zwrotnego jest wykonywana po zakończeniu działania modułów obsługi zdarzeń, ale tuż przed ponownym obliczeniem stylu i pracą nad układem.

Jeśli wykonywane w nich działania są złożone, ich wykonanie może zająć dużo czasu. Jeśli podejrzewasz, że wysokie wartości opóźnienia prezentacji wynikają z pracy, którą wykonujesz w usłudze requestAnimationFrame, możesz użyć danych LoAF dostępnych w bibliotece Web-vitals, aby zidentyfikować te scenariusze:

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 543.1999999880791

  // Get the longest script from the last LoAF entry:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.sort((a, b) => b.duration - a.duration)[0];

  // Get the render start time and when style and layout began:
  const {renderStart} = loaf;         // 2489
  const {styleAndLayoutStart} = loaf; // 2989.5999999940395

  // Calculate the `requestAnimationFrame` callback's duration:
  const rafDuration = styleAndLayoutStart - renderStart; // 500.59999999403954

  if (script) {
    // Get attribution for the event handler that triggered
    // the long-running requestAnimationFrame callback:
    const {invokerType} = script;        // 'user-callback'
    const {invoker} = script;            // 'FrameRequestCallback'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

Jeśli stwierdzisz, że znaczna część czasu opóźnienia prezentacji jest poświęcana w wywołaniu zwrotnym requestAnimationFrame, upewnij się, że Twoja praca w tych wywołaniach zwrotnych jest ograniczona do prac, które skutkują faktyczną aktualizacją interfejsu użytkownika. Wszelkie inne elementy, które nie mają wpływu na DOM ani style aktualizacji, niepotrzebnie opóźniają malowanie następnej klatki, więc zachowaj ostrożność.

Podsumowanie

Dane terenowe to najlepsze źródło informacji, na podstawie których możesz się dowiedzieć, które interakcje stanowią problem dla rzeczywistych użytkowników w tej dziedzinie. Korzystając z narzędzi do zbierania danych terenowych, takich jak biblioteka JavaScript Web-vitals (lub dostawca RUM), możesz mieć większą pewność, które interakcje są najbardziej problematyczne. Dzięki temu możesz odtworzyć problematyczne interakcje w module i poprawić je.

Baner powitalny z kanału Unsplash, autor: Federico Respini.