Optymalizuj opóźnienie wejściowe

Dowiedz się, czym jest opóźnienie sygnału wejściowego, i poznaj techniki, które pozwolą Ci je zmniejszyć i poprawić interaktywność.

Jeremy Wagner
Jeremy Wagner

Interakcje w internecie to złożone zagadnienie, a każda aktywność w przeglądarce jest generowana z powodu różnych działań. Ich wspólną cechą jest jednak to, że następują opóźnienia przed uruchomieniem wywołań zwrotnych zdarzeń. Z tego przewodnika dowiesz się, czym jest opóźnienie wprowadzania danych i jak je zminimalizować, aby przyspieszyć interakcję z witryną.

Co to jest opóźnienie sygnału?

Opóźnienie wejściowe to czas od pierwszej interakcji użytkownika ze stroną – np. kliknięcia ekranu, kliknięcia myszą lub naciśnięcia klawisza – aż do rozpoczęcia wywołania zwrotnego zdarzenia. Każda interakcja zaczyna się od pewnego opóźnienia na wejściu.

Uproszczona wizualizacja opóźnienia wejścia. Po lewej stronie znajduje się grafika liniowa kursora myszy z wybuchem gwiazdy, symbolizująca początek interakcji. Po prawej znajduje się grafika liniowa koła zębatego, wskazująca rozpoczęcie uruchamiania modułów obsługi zdarzeń interakcji. Odstęp między nimi jest traktowany jako opóźnienie danych wejściowych z nawiasem klamrowym.
Mechanika opóźnienia wejścia. System operacyjny odbierze dane wejściowe do przeglądarki, zanim nastąpi interakcja. Zajmie to określony czas i można go zwiększyć, wykonując obecną pracę z wątkiem głównym.

Części opóźnienia wprowadzania danych są nieuniknione. System operacyjny zawsze potrzebuje trochę czasu na rozpoznanie zdarzenia wejściowego i przekazanie go do przeglądarki. Jednak taka część opóźnienia wprowadzania danych często jest nawet niezauważalna, a na stronie występują też inne czynniki, które mogą powodować opóźnienia na tyle długie, aby powodować problemy.

Co zrobić o opóźnieniu wprowadzania

Ogólnie rzecz biorąc, każda część interakcji powinna być jak najkrótsza, aby witryna miała jak największe szanse na osiągnięcie progu „dobrej” interakcji z następnym wyrenderowaniem (INP), niezależnie od urządzenia użytkownika. Kontrola opóźnienia sygnału to tylko jeden z elementów osiągnięcia tego progu.

Być może zastanawiasz się, czy warto sprawdzić wartości progowe opóźnienia przy pierwszym działaniu (FID), aby określić, na jakie opóźnienia mogą wystąpić sygnały wejściowe. Jednak próg „dobrego” dla FID wynosi 100 milisekund lub mniej. Jeśli przekroczysz ten próg, przeznaczysz połowę budżetu na samo opóźnienie danych wejściowych. Jest to niezalecane, jeśli uważasz, że interakcja wymaga też czasu na wywołanie zwrotne zdarzenia i wyświetlenie przez przeglądarkę następnej klatki.

Aby osiągnąć „dobry” próg INP, należy dążyć do jak najkrótszego opóźnienia sygnału, ale nie należy oczekiwać jego całkowitego wyeliminowania, ponieważ jest to niemożliwe. Jeśli nie chcesz wykonywać zbyt wielu zadań z wątkiem głównym, gdy użytkownicy próbują wchodzić w interakcję z Twoją stroną, opóźnienie wprowadzania powinno być na tyle małe, aby uniknąć problemów.

Jak zminimalizować opóźnienie sygnału

Jak już wspomnieliśmy, pewnego opóźnienia nie da się uniknąć, ale z drugiej strony można je uniknąć. Oto kilka rzeczy, które warto wziąć pod uwagę, jeśli masz problemy z dużymi opóźnieniami w wprowadzaniu danych.

Nie używaj powtarzających się minutników, które uruchamiają nadmierną pracę w wątku głównym.

W języku JavaScript są dwie często używane funkcje licznika czasu, które mogą przyczyniać się do opóźnienia danych wejściowych: setTimeout i setInterval. Różnica między nimi polega na tym, że setTimeout zaplanuje wywołanie zwrotne po określonym czasie. setInterval natomiast zaplanuje wywołanie zwrotne, które będzie wykonywane bezterminowo co n milisekund lub do momentu zatrzymania licznika za pomocą przycisku clearInterval.

Usługa setTimeout sama w sobie nie stanowi problemu – może pomagać w unikaniu długich zadań. Zależy to jednak od tego, kiedy przekroczono limit czasu oczekiwania i czy użytkownik spróbuje wejść w interakcję ze stroną po upływie limitu czasu oczekiwania na wywołanie zwrotne.

Poza tym setTimeout może być uruchamiany w pętli lub rekurencyjnie, gdzie działa bardziej jak element setInterval, ale najlepiej nie planować kolejnej iteracji do czasu zakończenia poprzedniej. Oznacza to, że pętla będzie przekazywała wątek główny przy każdym wywołaniu metody setTimeout, jednak należy zadbać o to, aby wywołanie zwrotne nie powodowało nadmiernej pracy.

Funkcja setInterval uruchamia wywołanie zwrotne w określonym przedziale czasu, więc znacznie bardziej prawdopodobne jest, że przeszkadza w interakcjach. Dzieje się tak, ponieważ w przeciwieństwie do pojedynczego wystąpienia wywołania setTimeout, które jest jednorazowym wywołaniem zwrotnym, które może przeszkadzać w interakcji z użytkownikiem, powtarzający się charakter elementu setInterval znacznie zwiększa prawdopodobieństwo, że przeszkodzi ono w interakcji, zwiększając w ten sposób opóźnienie interakcji.

Zrzut ekranu przedstawiający narzędzie do profilowania wydajności w Narzędziach deweloperskich w Chrome, przedstawiające opóźnienie wprowadzania. Zadanie wywoływane przez funkcję licznika czasu zachodzi tuż przed inicjowaniem interakcji użytkownika polegającej na kliknięciu. Licznik czasu zwiększa jednak opóźnienie sygnału wejściowego, przez co wywołania zwrotne zdarzenia interakcji są wykonywane później niż zwykle.
Minutnik zarejestrowany przez poprzednie wywołanie setInterval przyczyniający się do opóźnienia sygnału, widoczne na panelu wydajności Chrome DevTools. Dodatkowe opóźnienie danych wejściowych powoduje, że wywołania zwrotne zdarzenia dla tej interakcji są wykonywane później, niż byłoby to możliwe.

Jeśli liczniki czasu występują w kodzie własnym, masz nad nimi kontrolę. Zastanów się, czy są one potrzebne, czy możesz zrobić wszystko, aby ograniczyć nakład pracy. Jednak liczniki czasu w skryptach innych firm to coś innego. Często nie masz kontroli nad działaniem zewnętrznego skryptu, a rozwiązywanie problemów z wydajnością kodu zewnętrznego wiąże się często z współpracą z zainteresowanymi osobami w celu określenia, czy dany skrypt zewnętrzny jest potrzebny. Jeśli tak, skontaktuj się z tym dostawcą, by ustalić, co można zrobić w celu rozwiązania problemów z wydajnością, jakie mogą powodować w witrynie.

Unikaj długich zadań

Jednym ze sposobów na uniknięcie dużych opóźnień związanych z wprowadzaniem danych jest unikanie długich zadań. Jeśli nadmierna ilość pracy z wątkiem głównym blokuje ten wątek podczas interakcji, zwiększy to opóźnienie danych wejściowych, zanim nastąpi zakończenie długich zadań.

Wizualizacja tego, jak długo zadania wydłużają opóźnienie wprowadzania. Interakcja ma miejsce wkrótce po uruchomieniu jednego długiego zadania, co powoduje znaczne opóźnienie w danych wejściowych i sprawia, że wywołania zwrotne zdarzeń są wykonywane znacznie później niż powinny. Na samym dole interakcja ma miejsce mniej więcej w tym samym czasie, ale długie zadanie zostaje podzielone na kilka mniejszych z powodu ich zwracania się, dzięki czemu wywołania zwrotne zdarzenia interakcji są wykonywane znacznie szybciej.
Wizualizacja, co dzieje się z interakcjami, gdy zadania są zbyt długie, a przeglądarka nie potrafi dostatecznie szybko reagować na interakcje, gdy dłuższe zadania są dzielone na mniejsze zadania.

Poza ograniczeniem ilości pracy wykonywanej przez użytkownika (i zawsze staraj się robić jak najmniejszą ilość pracy w wątku głównym), możesz poprawić reagowanie na działania użytkowników przez dzielenie długich zadań.

Zwróć uwagę na pokrywanie się interakcji

Szczególnie trudnym aspektem optymalizacji INP może być sytuacja, w której występują interakcje, które nakładają się na siebie. Pokrywanie się interakcji oznacza, że po interakcji z jednym elementem inicjujesz kolejną interakcję ze stroną, zanim początkowa interakcja mogła wyrenderować następną klatkę.

Ilustracja pokazująca, kiedy zadania mogą się pokrywać, co powoduje duże opóźnienia w dostarczaniu danych. Na tej ilustracji interakcja polegająca na kliknięciu nakłada się na interakcję z kluczem, by zwiększyć opóźnienie reakcji interakcji z klawiszem.
Wizualizacja 2 równoczesnych interakcji w narzędziu do profilu wydajności w Narzędziach deweloperskich w Chrome. Renderowanie za pierwszym razem związane z kliknięciem powoduje opóźnienie w przypadku kolejnej interakcji z klawiaturą.

Źródła interakcji mogą być tak proste, jak użytkownicy wykonujący wiele interakcji w krótkim czasie. Może się tak zdarzyć, gdy użytkownicy wpisują pola formularza, które w krótkim czasie mogą wymagać wielu interakcji z klawiatury. Jeśli praca nad kluczowym zdarzeniem jest szczególnie kosztowna – na przykład w częstym przypadku pól autouzupełniania, w których żądania sieciowe są wysyłane do backendu, masz kilka możliwości:

  • Rozważ odrzucenie danych wejściowych, aby ograniczyć liczbę wywołań zwrotnych zdarzenia w danym okresie.
  • AbortController pozwala anulować wychodzące żądania fetch, aby wątek główny nie przeciążył się obsługą wywołań zwrotnych fetch. Uwaga: właściwości signal instancji AbortController może też służyć do przerywania zdarzeń.

Innym źródłem zwiększonego opóźnienia wprowadzania z powodu nakładających się interakcji mogą być drogie animacje. Animacje w języku JavaScript mogą w szczególności uruchamiać wiele wywołań requestAnimationFrame, które mogą przeszkadzać użytkownikom w interakcjach. Aby obejść ten problem, używaj animacji CSS, gdy tylko jest to możliwe. Pozwoli to uniknąć kolejki potencjalnie drogich klatek animacji. Jeśli jednak to zrobisz, unikaj nieskomponowanych animacji, aby były one uruchamiane głównie w wątkach GPU i kompozytora, a nie w wątku głównym.

Podsumowanie

Chociaż opóźnienia wprowadzania danych mogą nie odpowiadać większości czasu na przetworzenie interakcji, pamiętaj, że każda jej część może zająć sporo czasu, który możesz zredukować. Jeśli obserwujesz duże opóźnienie wprowadzania danych, możesz je zmniejszyć. Unikanie powtarzających się wywołań zwrotnych licznika czasu, dzielenie długich zadań i świadomość potencjalnego pokrywania się interakcji mogą pomóc zredukować opóźnienie wprowadzania, co z kolei przełoży się na szybszą interaktywność użytkowników witryny.

Baner powitalny z albumu Unsplash autorstwa Erika Mcleana.