Znajdowanie powolnych interakcji w terenie

Dowiedz się, jak znaleźć w danych polowych witryny powolne interakcje, aby móc znaleźć możliwości poprawy czasu od interakcji do kolejnego wyrenderowania.

Dane z pola to dane, które informują, jak użytkownicy korzystają z Twojej witryny. Wykrywa problemy, których nie można znaleźć tylko w danych laboratoryjnych. W przypadku interakcji do kolejnego wyrenderowania (INP) dane w polu są niezbędne do identyfikowania wolnych interakcji i zawierają ważne wskazówki, które pomogą Ci je rozwiązać.

Z tego przewodnika dowiesz się, jak szybko ocenić INP witryny za pomocą danych z pól w Raporcie na temat użytkowania Chrome (CrUX), aby sprawdzić, czy Twoja witryna ma problemy z tą metryką. Następnie dowiesz się, jak używać wersji atrybucji biblioteki JavaScript web-vitals oraz nowych statystyk, które udostępnia ona dzięki interfejsowi Long Animation Frames API (LoAF), do zbierania i interpretowania danych polowych dotyczących powolnych interakcji w Twojej witrynie.

Zacznij od korzystania z CrUX, aby ocenić INP swojej witryny

Jeśli nie zbierasz danych zgromadzonych od użytkowników witryny, możesz zacząć od raportu CrUX. Raport ten zbiera dane z pól od rzeczywistych użytkowników Chrome, którzy wyrazili zgodę na wysyłanie danych telemetrycznych.

Dane CrUX są wyświetlane w różnych obszarach w zależności od zakresu informacji, których szukasz. Narzędzie CrUX może udostępniać dane o INP i innych podstawowych wskaźnikach internetowych w przypadku:

  • pojedyncze strony i całe źródła za pomocą PageSpeed Insights;
  • Typy stron. Na przykład wiele witryn e-commerce ma strony typu „Strona szczegółów produktu” i „Strona z informacjami o produkcie”. Dane z bazy danych CrUX możesz pobierać w Search Console w przypadku unikalnych typów stron.

Na początek możesz wpisać adres URL swojej witryny w PageSpeed Insights. Po wpisaniu adresu URL dane z tego pola (jeśli są dostępne) będą wyświetlane w przypadku wielu wskaźników, m.in. INP. Możesz też użyć przełączników, aby sprawdzić wartości INP dla wymiarów urządzeń mobilnych i komputerów.

Dane z pola wyświetlane przez CrUX w narzędzie PageSpeed Insights, w tym LCP, INP i CLS jako 3 podstawowe wskaźniki internetowe, TTFB i FCP jako dane diagnostyczne oraz FID jako wycofany podstawowy wskaźnik internetowy.
Wyświetlanie danych CrUX w narzędziu PageSpeed Insights. W tym przykładzie INP danej strony internetowej wymaga poprawy.

Te dane są przydatne, ponieważ informują, czy masz problem. Nie może jednak powiedzieć, co powoduje problemy. Dostępnych jest wiele rozwiązań do monitorowania rzeczywistych użytkowników (RUM), które pomogą Ci zbierać własne dane z pól użytkowników witryny. Dzięki temu możesz uzyskać odpowiedź na to pytanie. Jedną z opcji jest samodzielne zbieranie tych danych za pomocą biblioteki JavaScript web-vitals.

Zbieranie danych polowych za pomocą biblioteki JavaScript web-vitals

web-vitalsBiblioteka JavaScriptu to skrypt, który możesz załadować w witrynie, aby zbierać dane pól od użytkowników witryny. Możesz go używać do rejestrowania różnych danych, w tym INP w przeglądarkach, które je obsługują.

Obsługa przeglądarek

  • Chrome: 96.
  • Edge: 96.
  • Firefox: nieobsługiwane.
  • Safari: nieobsługiwane.

Źródło

Standardowa wersja biblioteki web-vitals może służyć do uzyskiwania podstawowych danych INP od użytkowników w polu:

import {onINP} from 'web-vitals';

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

Aby przeanalizować dane z pola, które otrzymujesz od użytkowników, musisz je gdzieś wysłać:

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 te dane same w sobie nie dają Ci więcej informacji niż CrUX. Właśnie w tym miejscu do akcji wkracza wersja atrybucji biblioteki web-vitals.

Wykorzystanie biblioteki web-vitals do tworzenia atrybucji

Budowanie atrybucji w bibliotece web-vitals pozwala wyświetlać dodatkowe dane, które możesz uzyskać od użytkowników, aby lepiej rozwiązywać problemy z interaktywnością, które wpływają na INP witryny. Te dane są dostępne za pomocą obiektu attribution wyświetlanego w metodzie onINP() biblioteki:

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

onINP(({name, value, rating, attribution}) => {
  console.log(name);         // 'INP'
  console.log(value);        // 56
  console.log(rating);       // 'good'
  console.log(attribution);  // Attribution data object
});
Jak wyglądają logi konsoli z biblioteki web-vitals W tym przykładzie konsola pokazuje nazwę wskaźnika (INP), wartość INP (56), która mieści się w progach INP (dobrze), oraz różne informacje wyświetlane w obiekcie atrybucji, w tym wpisy z interfejsu Long Animation Frames API.
Jak dane z biblioteki web-vitals są wyświetlane w konsoli.

Oprócz INP strony kompilacja atrybucji zawiera też wiele danych, które pomogą Ci zrozumieć przyczyny powolnego działania interakcji, m.in. na której części interakcji powinieneś się skupić. Pomaga ono odpowiadać na ważne pytania, takie jak:

  • „Czy użytkownik wchodził w interakcję ze stroną podczas jej wczytywania?”
  • „Czy przetwarzanie zdarzeń interakcji trwało długo?”
  • „Czy kod obsługi interakcji został opóźniony?” If so, what else was happening on the main thread at that time?"
  • „Czy interakcja spowodowała dużą ilość renderowania, która opóźniła wyświetlenie następnej klatki?”

W tabeli poniżej znajdziesz podstawowe dane atrybucji, które możesz uzyskać z biblioteki. Mogą one pomóc Ci określić ogólne przyczyny powolnego działania Twojej witryny:

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 wpisy na klawiaturze.
inputDelay* Opóźnienie wejściowe interakcji.
processingDuration* Czas od momentu, gdy pierwszy odbiorca zdarzeń zaczął działać w odpowiedzi na interakcję użytkownika, do momentu zakończenia przetwarzania odbiornika zdarzeń.
presentationDelay* Opóźnienie wyświetlania interakcji, które występuje od momentu zakończenia działania obsługi zdarzeń do momentu wyświetlenia następnego kadru.
longAnimationFrameEntries* Wpisy z LoAF powiązane z interakcją. Więcej informacji znajdziesz w następnej sekcji.
*Nowość w wersji 4

Począwszy od wersji 4 biblioteki web-vitals możesz uzyskać jeszcze bardziej szczegółowe informacje o problematycznych interakcjach dzięki danym z podziałem na fazy INP (opóźnienie wejścia, czas przetwarzania i opóźnienie wyświetlania) oraz interfejsowi Long Animation Frames API (LoAF).

Long Animation Frames API (LoAF)

Obsługa przeglądarek

  • Chrome: 123.
  • Edge: 123.
  • Firefox: nieobsługiwane.
  • Safari: nieobsługiwane.

Źródło

Debugowanie interakcji za pomocą danych z pola jest trudnym zadaniem. Dzięki danym z LoAF możesz teraz uzyskać lepsze informacje o przyczynach powolnych interakcji, ponieważ LoAF udostępnia szczegółowe informacje o czasie trwania i inne dane, które możesz wykorzystać do dokładnego określenia przyczyn, a co ważniejsze, źródła problemu w kodzie Twojej witryny.

Wersja atrybucji biblioteki web-vitals udostępnia tablicę wpisów LoAF pod kluczem longAnimationFrameEntries obiektu attribution. W tabeli poniżej znajdziesz kilka kluczowych informacji, które możesz znaleźć w każdym wpisie w LoAF:

Klucz obiektu wpisu LoAF Dane
duration Czas trwania długiej klatki animacji do momentu zakończenia układu, z wyłączeniem malowania i kompozytowania.
blockingDuration Łączny czas w ramach interwału, w którym przeglądarka nie mogła szybko odpowiadać z powodu długich zadań. Ten czas blokowania może obejmować długie zadania wykonywane przez JavaScript, a także wszelkie kolejne długie zadania renderowania w ramach danego interwału.
firstUIEventTimestamp Sygnatura czasowa określająca, kiedy zdarzenie zostało dodane do kolejki w ramach ramki. Przydatne do określenia opóźnienia wejścia na początku interakcji.
startTime Sygnatura czasowa rozpoczęcia klatki.
renderStart Kiedy rozpoczęto renderowanie danej klatki. Obejmuje to wszystkie wywołania requestAnimationFrame (a także wywołania ResizeObserver, jeśli to konieczne), ale przed rozpoczęciem pracy nad stylem lub układem.
styleAndLayoutStart Gdy w ramce występują zmiany stylu lub układu. Może być przydatne do określenia długości pracy nad stylem lub układem, gdy uwzględnisz inne dostępne sygnatury czasowe.
scripts Tablica elementów zawierająca informacje o przypisaniu skryptu, które przyczyniają się do INP strony.
Wizualizacja długiej klatki animacji zgodnie z modelem LoAF.
Diagram przedstawiający czas trwania długiej klatki animacji zgodnie z interfejsem LoAF API (z wyjątkiem blockingDuration).

Wszystkie te informacje mogą wiele powiedzieć o tym, co powoduje spowolnienie interakcji, ale szczególne znaczenie ma tablica scripts, którą wyświetlają wpisy LoAF:

Klucz obiektu atrybucji skryptu Dane
invoker Wywołujący. Może się to różnić w zależności od typu wywołującego opisanego w kolejnym wierszu. Przykładami wywołań mogą być wartości takie jak 'IMG#id.onload', 'Window.requestAnimationFrame' lub 'Response.json.then'.
invokerType Typ wywołującego. Może to być '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, którą wskazuje sourceURL.
sourceFunctionName Nazwa funkcji w wykrytym skrypcie.

Każdy wpis w tym tablicy zawiera dane widoczne w tej tabeli, które dostarczają informacji o skrypcie odpowiedzialnym za wolne działanie interakcji oraz o przyczynie tego stanu.

Pomiar i identyfikowanie typowych przyczyn powolnego działania

Aby pokazać Ci, jak możesz wykorzystywać te informacje, pokażemy Ci w tym przewodniku, jak używać danych LoAF wyświetlanych w bibliotece web-vitals, aby określić przyczyny powolnego działania interakcji.

Długi czas przetwarzania

Czas przetwarzania interakcji to czas, jaki zajmuje wykonanie zarejestrowanych wywołań zwrotnych modułu obsługi zdarzeń, oraz wszystko, co może się zdarzyć między nimi. Biblioteka web-vitals wyświetla długi czas przetwarzania:

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

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

Naturalnie można pomyśleć, że główną przyczyną powolnego działania jest zbyt długi czas wykonywania kodu w obsługującym zdarzenie komponencie, ale nie zawsze tak jest. Gdy potwierdzisz, że to jest 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 korzystać z danych LoAF, aby ustalić dokładną przyczynę interakcji z wysokimi wartościami czasu przetwarzania, m.in.:

  • Element i zarejestrowany detektor zdarzeń.
  • Plik skryptu (oraz pozycja znaku w nim) zawierający długotrwały kod obsługi zdarzenia.
  • Nazwa funkcji.

Te dane są bezcenne. Nie musisz już dowiadywać się, która interakcja lub który z obejmujących ją event handlerów odpowiada za długi czas przetwarzania. Skrypty innych firm często mogą rejestrować własne moduły obsługi zdarzeń, więc możesz określić, czy to Twój kod był odpowiedzialny za problem. W przypadku kodu, nad którym masz kontrolę, warto zoptymalizować długie zadania.

Długie opóźnienia w reakcji na działanie

Chociaż długotrwałe metody obsługi zdarzeń są powszechne, należy wziąć pod uwagę inne części interakcji. Jedna część występuje przed przetwarzaniem, co nazywamy opóźnieniem wejścia. Jest to czas od momentu, gdy użytkownik rozpoczyna interakcję, do momentu, gdy zaczynają się wywołania zwrotne jego funkcji obsługi zdarzeń. Występuje on, gdy główny wątek przetwarza już inne zadanie. Dzięki budowie atrybucji biblioteki web-vitals możesz sprawdzić czas opóźnienia wprowadzania danych w przypadku interakcji:

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

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

Jeśli zauważysz, że niektóre interakcje mają długie opóźnienia w wejściu, musisz ustalić, co działo się na stronie w momencie interakcji, która spowodowała długie opóźnienie w wejściu. Często zależy to od tego, czy interakcja miała miejsce podczas wczytywania strony, czy później.

Czy wystąpiło to podczas wczytywania strony?

Wątek główny jest często najbardziej obciążony podczas wczytywania strony. W tym czasie wszystkie zadania są umieszczane w kolejce i przetwarzane. Jeśli użytkownik spróbuje wejść na stronę, gdy trwa to działanie, może to opóźnić interakcję. Strony, które wczytują dużo kodu JavaScript, mogą inicjować kompilację i sprawdzanie skryptów, a także wykonywać funkcje, które przygotowują stronę do interakcji z użytkownikiem. Może to przeszkadzać, jeśli użytkownik wejdzie w interakcję z treścią w trakcie wykonywania tej czynności. Możesz sprawdzić, czy tak się dzieje 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 podczas rejestrowania tych danych w polu widzisz długie opóźnienia w składaniu danych i typy wywoływacza 'classic-script' lub 'module-script', można założyć, że skrypty na stronie długo się przetwarzają i blokują główny wątek na tyle długo, że opóźniają interakcje. Czas blokowania możesz skrócić, dzieląc skrypty na mniejsze pakiety, odkładając początkowo nieużywany kod do późniejszego wczytania oraz sprawdzając, czy w witrynie nie ma nieużywanego kodu, który można całkowicie usunąć.

Czy to nastąpiło po załadowaniu strony?

Opóźnienia w rejestrowaniu danych często występują podczas wczytywania strony, ale mogą też występować po jej wczytaniu z całkowicie innych przyczyn. Typowymi przyczynami opóźnień wprowadzania danych po załadowaniu strony może być kod, który jest uruchamiany okresowo z powodu wcześniejszego wywołania funkcji setInterval, a nawet wywołania zwrotne zdarzeń, które zostały umieszczone w kole do uruchomienia wcześniej i są nadal 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'
  }
});

Podobnie jak w przypadku rozwiązywania problemów z wysokimi wartościami czasu przetwarzania, długie opóźnienia w przyjmowaniu danych spowodowane wymienionymi wcześniej przyczynami pozwolą Ci uzyskać szczegółowe dane o przypisaniu skryptu. Różnica polega na tym, że typ wywołującego będzie się zmieniać w zależności od charakteru pracy, która opóźniła interakcję:

  • 'user-callback' wskazuje, że zadanie blokowania pochodziło z setInterval, setTimeout lub nawet requestAnimationFrame.
  • 'event-listener' oznacza, że zadanie blokujące pochodzi z wcześniejszego wejścia, które zostało umieszczone w kole i nadal jest przetwarzane.
  • 'resolve-promise''reject-promise' oznaczają, że zadanie blokujące pochodziło z niektórych asynchronicznych działań, które zostały rozpoczęte wcześniej i rozwiązane lub odrzucone w momencie, gdy użytkownik próbował wejść w interakcję ze stroną, opóźniając ją.

W każdym razie dane atrybucji skryptu pomogą Ci określić, od czego zacząć szukać problemu i czy opóźnienie w wprowadzaniu danych było spowodowane Twoim kodem czy skryptem innej firmy.

Długie opóźnienia prezentacji

Opóźnienia wyświetlania to ostatni etap interakcji. Rozpoczynają się one po zakończeniu działania obsługi zdarzeń interakcji i trwają do momentu, w którym zostanie narysowany następny kadr. Występują one, gdy działanie w obiekcie event handler z powodu interakcji zmienia wizualny stan interfejsu użytkownika. Podobnie jak w przypadku czasu przetwarzania i opóźnień wprowadzania danych, biblioteka web-vitals może poinformować, jak długie było opóźnienie wyświetlania w przypadku danej interakcji:

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

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

Jeśli rejestrujesz te dane i obserwujesz duże opóźnienia w prezentowaniu interakcji, które przyczyniają się do zwiększenia INP witryny, przyczyny mogą być różne, ale warto zwrócić uwagę na kilka z nich.

kosztowne prace związane ze stylem i układem;

Długie opóźnienia w prezentacji mogą być spowodowane kosztownym przeliczaniem stylówukładem, które wynikają z różnych przyczyn, w tym złożonych selektorów CSS i dużych rozmiarów DOM. Czas trwania tego procesu możesz mierzyć za pomocą funkcji pomiaru czasu LoAF dostępnej 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 podaje czasu trwania pracy nad stylem i układem danej klatki, ale informuje, kiedy rozpoczęła się ta praca. Na podstawie tego sygnatury czasowej początkowej możesz użyć innych danych z LoAF, aby obliczyć dokładny czas trwania tego zadania. W tym celu określ czas zakończenia ramki i odejmij od niego sygnaturę czasową początkową zadania związanego ze stylem i układem.

Długotrwałe requestAnimationFramewywołania zwrotne

Jedną z potencjalnych przyczyn długich opóźnień w prezentacji może być nadmierna ilość pracy wykonanej w ramach wywołania zwrotnego requestAnimationFrame. Treść tej funkcji zwracanej jest wykonywana po zakończeniu działania obsługi zdarzeń, ale tuż przed ponownym obliczeniem stylu i ułożeniem.

Jeśli wykonywane w nich zadania są skomplikowane, wywołania zwrotne mogą zająć sporo czasu. Jeśli podejrzewasz, że wysokie wartości opóźnienia prezentacji są spowodowane pracą nad requestAnimationFrame, możesz użyć danych LoAF udostępnionych przez bibliotekę 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 zauważysz, że znaczna część czasu opóźnienia prezentacji jest wykorzystywana na wywołania zwrotne requestAnimationFrame, upewnij się, że podczas tych wywołań wykonywane są tylko czynności, które prowadzą do faktycznej aktualizacji interfejsu użytkownika. Inne działania, które nie wpływają na DOM ani nie aktualizują stylów, niepotrzebnie opóźnią wyświetlanie kolejnego klatki, więc bądź ostrożny.

Podsumowanie

Dane z pola to najlepsze źródło informacji, z którego możesz korzystać, aby dowiedzieć się, które interakcje są problematyczne dla rzeczywistych użytkowników. Korzystając z narzędzi do zbierania danych polowych, takich jak biblioteka JavaScripta web-vitals (lub dostawca RUM), możesz dokładniej określić, które interakcje są najbardziej problematyczne, a potem odtworzyć je w laboratorium i je naprawić.

Baner powitalny z Unsplash: Federico Respini.