Znajdowanie powolnych interakcji w terenie

Dowiedz się, jak znaleźć w danych z terenu informacje o powolnych interakcjach w witrynie, aby znaleźć możliwości poprawy czasu od interakcji do kolejnego wyrenderowania.

Dane z terenu to dane, które pokazują, jak prawdziwi użytkownicy korzystają z Twojej witryny. Ujawnia problemy, których nie można znaleźć tylko na podstawie danych laboratoryjnych. W przypadku interakcji do kolejnego wyrenderowania (INP) dane z terenu są niezbędne do identyfikowania powolnych interakcji i dostarczają istotnych wskazówek, które pomagają je naprawić.

Z tego przewodnika dowiesz się, jak szybko ocenić INP witryny za pomocą danych z pól z Raportu na temat użytkowania Chrome (CrUX), aby sprawdzić, czy witryna ma problemy z INP. Następnie dowiesz się, jak używać wersji atrybucyjnej biblioteki JavaScript web-vitals i nowych statystyk, które zapewnia interfejs Long Animation Frames API (LoAF), do zbierania i interpretowania danych z terenu dotyczących powolnych interakcji w Twojej witrynie.

Zacznij od CrUX, aby ocenić INP witryny

Jeśli nie zbierasz danych z witryny od użytkowników, CrUX może być dobrym punktem wyjścia. CrUX zbiera dane z pól od rzeczywistych użytkowników Chrome, którzy wyrazili zgodę na wysyłanie danych telemetrycznych.

Dane CrUX są dostępne w wielu różnych miejscach, w zależności od zakresu informacji, których szukasz. CrUX może dostarczać dane o INP i innych podstawowych wskaźnikach internetowych w przypadku:

  • poszczególne strony i całe domeny za pomocą PageSpeed Insights;
  • Rodzaje stron. Wiele witryn e-commerce ma na przykład typy stron „Strona z informacjami o produkcie” i „Strona z listą produktów”. Dane CrUX 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. Po wpisaniu adresu URL wyświetlą się dane z pola (jeśli są dostępne) dla wielu wskaźników, w tym INP. Możesz też użyć przełączników, aby sprawdzić wartości INP dla wymiarów na urządzeniach mobilnych i komputerach.

Dane z terenu wyświetlane przez CrUX w PageSpeed Insights, pokazujące LCP, INP i CLS jako 3 podstawowe wskaźniki internetowe oraz TTFB i FCP jako dane diagnostyczne, a także FID jako wycofany podstawowy wskaźnik internetowy.
Odczyt danych raportu na temat użytkowania Chrome widoczny w PageSpeed Insights. W tym przykładzie INP podanej strony wymaga poprawy.

Te dane są przydatne, ponieważ informują, czy występuje problem. CrUX nie może jednak określić, co powoduje problemy. Dostępnych jest wiele rozwiązań do monitorowania użytkowników (RUM), które pomogą Ci zbierać własne dane z pól od użytkowników Twojej witryny, aby uzyskać odpowiedź na to pytanie. Jedną z opcji jest samodzielne zbieranie tych danych za pomocą biblioteki JavaScript web-vitals.

Zbieranie danych o polach za pomocą biblioteki JavaScript web-vitals

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

Browser Support

  • Chrome: 96.
  • Edge: 96.
  • Firefox: 144.
  • Safari: not supported.

Source

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

import {onINP} from 'web-vitals';

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

Aby analizować dane z pól od użytkowników, musisz je gdzieś wysył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);
});

Same te dane nie mówią jednak nic więcej niż dane z raportu CrUX. W takich przypadkach przydaje się wersja biblioteki web-vitals do atrybucji.

Więcej możliwości dzięki wersji biblioteki web-vitals z atrybucją

Wersja atrybucji biblioteki web-vitals udostępnia dodatkowe dane, które możesz uzyskać od użytkowników w terenie, aby lepiej rozwiązywać problemy z interakcjami wpływającymi na INP Twojej witryny. Dostęp do tych danych można uzyskać za pomocą obiektu attribution, który jest udostępniany 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. Konsola w tym przykładzie pokazuje nazwę danych (INP), wartość INP (56), miejsce, w którym ta wartość znajduje się w zakresie progów INP (dobry), 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 samego INP strony atrybucja dostarcza wiele danych, które pomagają zrozumieć przyczyny powolnych interakcji, w tym wskazać, na której części interakcji należy się skupić. Może Ci pomóc w znalezieniu odpowiedzi 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 kod obsługi zdarzenia interakcji został opóźniony w rozpoczęciu? Jeśli tak, co jeszcze działo się w tym czasie w głównym wątku?”.
  • „Czy interakcja spowodowała dużo pracy związanej z renderowaniem, która opóźniła wyrenderowanie następnej klatki?”

W tabeli poniżej znajdziesz niektóre podstawowe dane o atrybucji, które możesz uzyskać z biblioteki. Pomogą Ci one określić ogólne przyczyny powolnych interakcji w Twojej witrynie:

attribution klucz obiektu Dane
interactionTarget Selektor CSS wskazujący element, który wygenerował wartość INP strony, np. button#save.
interactionType Rodzaj interakcji, np. kliknięcia, dotknięcia lub wpisywanie z klawiatury.
inputDelay* Opóźnienie wejściowe interakcji.
processingDuration* Czas od momentu, gdy pierwszy odbiornik zdarzeń zaczął działać w odpowiedzi na interakcję użytkownika, do momentu zakończenia przetwarzania wszystkich odbiorników zdarzeń.
presentationDelay* Opóźnienie prezentacji interakcji, które rozpoczyna się po zakończeniu działania obsługi zdarzeń i trwa do momentu narysowania następnej klatki.
longAnimationFrameEntries* Wpisy z LoAF powiązane z interakcją. Więcej informacji znajdziesz w następnej sekcji.
*Nowości w wersji 4

Od wersji 4 biblioteki web-vitals możesz uzyskać jeszcze więcej informacji o problematycznych interakcjach dzięki danym, które udostępnia ona w postaci podziału na fazy INP (opóźnienie wejścia, czas przetwarzania i opóźnienie wyświetlania) oraz interfejsu Long Animation Frames API (LoAF).

Long Animation Frames API (LoAF)

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: not supported.
  • Safari: not supported.

Source

Debugowanie interakcji za pomocą danych z pola jest trudnym zadaniem. Dzięki danym z LoAF można jednak uzyskać lepszy wgląd w przyczyny powolnych interakcji, ponieważ LoAF udostępnia wiele szczegółowych pomiarów czasu i innych danych, które pozwalają precyzyjnie określić przyczyny, a co ważniejsze, miejsce występowania problemu w kodzie witryny.

Wersja biblioteki web-vitals do atrybucji 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 LoAF:

Klucz obiektu wpisu 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, w którym przeglądarka nie mogła szybko zareagować z powodu długich zadań. Ten czas blokowania może obejmować długie zadania uruchamiające JavaScript, a także wszelkie kolejne długie zadania renderowania w ramce.
firstUIEventTimestamp Sygnatura czasowa zdarzenia w momencie umieszczenia go w kolejce w ramce. Przydatne do określania początku opóźnienia wejściowego interakcji.
startTime Sygnatura czasowa rozpoczęcia klatki.
renderStart Czas rozpoczęcia renderowania klatki. Obejmuje to wszystkie wywołania zwrotne requestAnimationFrame (i w odpowiednich przypadkach wywołania zwrotne ResizeObserver), ale potencjalnie przed rozpoczęciem prac nad stylem lub układem.
styleAndLayoutStart Gdy w ramce występuje praca związana ze stylem lub układem. Może być przydatne przy określaniu długości pracy nad stylem lub układem, gdy uwzględniasz inne dostępne sygnatury czasowe.
scripts Tablica elementów zawierająca informacje o atrybucji skryptu, które mają wpływ na INP strony.
Wizualizacja długiej klatki animacji zgodnie z modelem LoAF.
Diagram czasów długiej klatki animacji zgodnie z interfejsem LoAF API (bez blockingDuration).

Wszystkie te informacje mogą wiele powiedzieć o tym, co powoduje spowolnienie interakcji, ale szczególnie interesująca powinna być tablica scripts, którą ujawniają 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 następnym wierszu. Przykłady wywołań to 'IMG#id.onload', 'Window.requestAnimationFrame''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 określonym przez sourceURL.
sourceFunctionName Nazwa funkcji w zidentyfikowanym skrypcie.

Każdy wpis w tej tablicy zawiera dane widoczne w tej tabeli, które dostarczają informacji o skrypcie odpowiedzialnym za powolną interakcję i o tym, w jaki sposób do niej doszło.

Mierzenie i identyfikowanie typowych przyczyn powolnych interakcji

Aby dać Ci wyobrażenie o tym, jak możesz wykorzystać te informacje, w tym przewodniku pokażemy, jak za pomocą danych LoAF wyświetlanych w web-vitals możesz określić niektóre przyczyny powolnych interakcji.

Długi czas przetwarzania

Czas przetwarzania interakcji to czas, w którym wykonywane są zarejestrowane wywołania zwrotne modułu obsługi zdarzeń interakcji i wszystkie inne czynności, które mogą się zdarzyć w międzyczasie. Długie czasy przetwarzania są wykrywane przez bibliotekę web-vitals:

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

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

Naturalne jest myślenie, że główną przyczyną powolnej interakcji jest zbyt długie działanie kodu obsługi zdarzeń, ale nie zawsze tak jest. Gdy potwierdzisz, że to jest problem, możesz dokładniej go zbadać, korzystając z danych 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.toSorted((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 pracować z danymi LoAF, aby znaleźć dokładną przyczynę interakcji o wysokich wartościach czasu przetwarzania, w tym:

  • Element i zarejestrowany detektor zdarzeń.
  • Plik skryptu i pozycja znaku w nim, które zawierają kod długotrwałego modułu obsługi zdarzeń.
  • Nazwa funkcji.

Ten typ danych jest nieoceniony. Nie musisz już samodzielnie sprawdzać, która interakcja lub który z jej modułów obsługi zdarzeń odpowiada za wysokie wartości czasu przetwarzania. Ponadto skrypty innych firm często rejestrują własne procedury obsługi zdarzeń, więc możesz sprawdzić, czy to Twój kod był odpowiedzialny za zdarzenie. W przypadku kodu, nad którym masz kontrolę, warto przyjrzeć się optymalizacji długich zadań.

Długie opóźnienia wejściowe

Długotrwałe procedury obsługi zdarzeń są powszechne, ale warto wziąć pod uwagę inne aspekty interakcji. Jedna część występuje przed czasem przetwarzania i jest nazywana opóźnieniem wejściowym. Jest to czas od momentu, w którym użytkownik rozpoczyna interakcję, do momentu, w którym zaczynają działać wywołania zwrotne obsługi zdarzeń. Dzieje się to, gdy wątek główny przetwarza już inne zadanie. Wersja atrybucji biblioteki web-vitals może podać długość opóźnienia danych wejściowych 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ą duże opóźnienia, musisz ustalić, co działo się na stronie w momencie interakcji, która spowodowała długie opóźnienie. Często sprowadza się to do tego, czy interakcja nastąpiła podczas ładowania strony, czy po nim.

Czy wystąpił podczas wczytywania strony?

Główny wątek jest zwykle najbardziej obciążony podczas wczytywania strony. W tym czasie kolejkowane i przetwarzane są różnego rodzaju zadania. Jeśli użytkownik spróbuje wejść w interakcję ze stroną, gdy to wszystko się dzieje, może to opóźnić interakcję. Strony, które wczytują dużo kodu JavaScript, mogą rozpocząć kompilowanie i ocenianie skryptów, a także wykonywanie funkcji, które przygotowują stronę do interakcji z użytkownikiem. Może to przeszkadzać użytkownikowi, jeśli w trakcie tej czynności wejdzie w interakcję ze stroną. 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.toSorted((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 rejestrujesz te dane w terenie i widzisz duże opóźnienia we wprowadzaniu danych oraz typy wywołujących 'classic-script' lub 'module-script', można stwierdzić, że skrypty w Twojej witrynie długo się wykonują i blokują wątek główny na tyle długo, że opóźniają interakcje. Możesz skrócić ten czas blokowania, dzieląc skrypty na mniejsze pakiety, odraczając wczytywanie początkowo nieużywanego kodu na późniejszy moment i sprawdzając witrynę pod kątem nieużywanego kodu, który możesz całkowicie usunąć.

Czy to było po wczytaniu strony?

Opóźnienia we wprowadzaniu danych często występują podczas ładowania strony, ale mogą też pojawić się po jej załadowaniu z zupełnie innego powodu. Częstymi przyczynami opóźnień w reagowaniu na dane wejściowe po wczytaniu strony mogą być kod, który jest uruchamiany okresowo z powodu wcześniejszego wywołania setInterval, lub nawet wywołania zwrotne zdarzeń, które zostały umieszczone w kolejce 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.toSorted((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, duże opóźnienia wejściowe spowodowane wspomnianymi wcześniej przyczynami dostarczą Ci szczegółowych danych o atrybucji skryptu. Różnica polega jednak na tym, że typ wywołującego zmieni się w zależności od charakteru pracy, która opóźniła interakcję:

  • 'user-callback' oznacza, że zadanie blokowania pochodziło z setInterval, setTimeout lub nawet requestAnimationFrame.
  • 'event-listener' oznacza, że zadanie blokujące pochodziło z wcześniejszego wejścia, które zostało umieszczone w kolejce i jest nadal przetwarzane.
  • 'resolve-promise''reject-promise' oznaczają, że zadanie blokujące pochodziło z jakiejś pracy asynchronicznej, która została rozpoczęta wcześniej, a zakończyła się lub została odrzucona w momencie, gdy użytkownik próbował wejść w interakcję ze stroną, co spowodowało opóźnienie interakcji.

W każdym przypadku dane atrybucji skryptu pomogą Ci określić, od czego zacząć poszukiwania i czy opóźnienie wejściowe było spowodowane Twoim kodem, czy skryptem innej firmy.

Długie opóźnienia prezentacji

Opóźnienia prezentacji to ostatni etap interakcji. Rozpoczynają się po zakończeniu działania obsługi zdarzeń interakcji i trwają do momentu narysowania następnej klatki. Występują one, gdy praca w obsłudze zdarzeń spowodowana interakcją zmienia stan wizualny interfejsu. Podobnie jak w przypadku czasu przetwarzania i opóźnień wejściowych, biblioteka web-vitals może podać czas opóźnienia prezentacji w przypadku interakcji:

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

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

Jeśli rejestrujesz te dane i widzisz duże opóźnienia w wyświetlaniu interakcji, które mają wpływ na INP Twojej witryny, przyczyny mogą być różne, ale oto kilka, na które warto zwrócić uwagę.

Kosztowna praca nad stylem i układem

Długie opóźnienia w prezentacji mogą być spowodowane kosztownym ponownym obliczaniem stylówukładu, które wynikają z wielu przyczyn, w tym złożonych selektorów CSS i dużych rozmiarów DOM. Możesz zmierzyć czas trwania tej pracy za pomocą pomiarów LoAF udostępnianych 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.toSorted((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 klatki, ale informuje, kiedy się ona rozpoczęła. Na podstawie tego znacznika czasu możesz użyć innych danych z LoAF, aby obliczyć dokładny czas trwania pracy. Wystarczy, że określisz czas zakończenia klatki i odejmiesz od niego znacznik czasu rozpoczęcia pracy nad stylem i układem.

Długotrwałe wywołania zwrotne requestAnimationFrame

Jedną z potencjalnych przyczyn długich opóźnień w wyświetlaniu jest nadmierna praca wykonywana w wywołaniu zwrotnym requestAnimationFrame. Zawartość tego wywołania zwrotnego jest wykonywana po zakończeniu działania procedur obsługi zdarzeń, ale tuż przed ponownym obliczeniem stylu i pracą związaną z układem.

Jeśli wykonywana w nich praca jest złożona, wywołania zwrotne mogą trwać dość długo. Jeśli podejrzewasz, że wysokie wartości opóźnienia prezentacji są spowodowane pracą, którą wykonujesz w requestAnimationFrame, możesz użyć danych LoAF udostępnianych 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.toSorted((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 poświęcana na requestAnimationFramewywołanie zwrotne, upewnij się, że w tych wywołaniach zwrotnych wykonujesz tylko działania, które powodują rzeczywistą aktualizację interfejsu. Wszelkie inne działania, które nie wpływają na DOM ani nie aktualizują stylów, niepotrzebnie opóźniają rysowanie następnej klatki, więc zachowaj ostrożność.

Podsumowanie

Dane z terenu 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 w terenie, takich jak biblioteka JavaScript web-vitals (lub dostawca RUM), możesz mieć większą pewność, które interakcje są najbardziej problematyczne. Następnie możesz odtworzyć problematyczne interakcje w laboratorium i je naprawić.

Baner powitalny z Unsplash, autor: Federico Respini.