Optymalizuj opóźnienie wejściowe

Dowiedz się, czym jest opóźnienie sygnału wejściowego, i poznaj techniki, które pozwalają go zmniejszyć i zwiększyć interaktywność.

Interakcje w internecie to skomplikowane zjawisko, a w przeglądarce do ich wywoływania zachodzi różnego rodzaju aktywność. Łączy je jednak to, że przed rozpoczęciem wywoływania wywołań zwrotnych zdarzeń wywołują opóźnienia w danych wejściowych. Z tego przewodnika dowiesz się, czym jest opóźnienie danych wejściowych i co możesz zrobić, aby je zminimalizować, aby zwiększyć szybkość interakcji w witrynie.

Co to jest opóźnienie danych wejściowych?

Opóźnienie danych wejściowych to okres, który rozpoczyna się od pierwszej interakcji użytkownika ze stroną, np. od kliknięcia ekranu, kliknięcia myszką lub naciśnięcia klawisza, do momentu rozpoczęcia wywoływania wywołania zwrotnego zdarzenia. Każda interakcja rozpoczyna się z pewnym opóźnieniem danych wejściowych.

Uproszczona wizualizacja opóźnienia danych wejściowych. Po lewej stronie znajduje się grafika z kreską kursora myszy, za nią – wybuchem gwiazdy, który symbolizuje początek interakcji. Po prawej stronie znajduje się grafika wiersza koła zębatego wskazującego, kiedy zaczynają działać moduły obsługi zdarzeń interakcji. Odstęp między nimi jest oznaczony jako opóźnienie sygnału wejściowego z nawiasem klamrowym.
Mechanika opóźnienia danych wejściowych. Po odebraniu danych wejściowych przez system operacyjny muszą one zostać przekazane do przeglądarki przed rozpoczęciem interakcji. Zajmuje to określony czas i może zostać wydłużony przez pracę w bieżącym wątku głównym.

Część opóźnień danych wejściowych jest nieunikniona: rozpoznanie zdarzenia wejściowego i przekazanie go do przeglądarki wymaga trochę czasu. Jednak ta część opóźnienia danych wejściowych jest często niezauważalna, a inne czynniki dzieją się na samej stronie, które mogą powodować tak duże opóźnienia danych wejściowych, że powodują problemy.

Jak wziąć pod uwagę opóźnienie sygnału wejściowego

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 dobrego wyrenderowania treści z interakcji do kolejnego wyrenderowania (INP) próg płatności niezależnie od urządzenia użytkownika. Kontrola opóźnienia danych wejściowych to tylko jeden z elementów osiągnięcia tego progu.

Dlatego musisz dążyć do najniższego możliwego opóźnienia sygnału wejściowego, który pozwoli osiągnąć „dobrą” wartość INP. i konkretnego progu. Pamiętaj jednak, że nie można w pełni wyeliminować opóźnień danych wejściowych. O ile unikasz nadmiernej pracy w wątku głównym, gdy użytkownicy próbują korzystać ze strony, opóźnienie danych wejściowych powinno być na tyle niewielkie, że pozwoli uniknąć problemów.

Jak zminimalizować opóźnienie sygnału wejściowego

Jak wspomnieliśmy wcześniej, pewnego opóźnienia sygnału wejściowego nie da się uniknąć, ale z drugiej strony można uniknąć takiego opóźnienia. Oto kilka rzeczy, które warto wziąć pod uwagę, jeśli masz duże opóźnienia w przesyłaniu danych.

Unikaj powtarzających się liczników czasu, które uruchamiają nadmierną ilość pracy w wątku głównym

W języku JavaScript są 2 najczęściej używane funkcje licznika czasu, które mogą zwiększać opóźnienie danych wejściowych: setTimeout i setInterval. Różnica między nimi polega na tym, że setTimeout planuje wywołanie zwrotne po określonym czasie. setInterval natomiast planuje wywołanie zwrotne co n milisekund w nieskończoność lub do momentu zatrzymania licznika przy użyciu clearInterval.

setTimeout sam w sobie nie jest problemem, a nawet może pomóc w unikaniu długich zadań. Zależy to jednak od tego, kiedy nastąpi przekroczenie limitu czasu i czy użytkownik spróbuje wejść w interakcję ze stroną po upływie tego czasu.

Poza tym funkcja setTimeout może być uruchamiana w pętli lub rekurencyjnie, gdzie działa podobnie jak setInterval, lepiej jednak nie planować następnej iteracji, dopóki nie zakończy się poprzednia. Oznacza to, że pętla zwraca się do wątku głównego przy każdym wywołaniu funkcji setTimeout. Zadbaj jednak o to, aby wywołanie zwrotne nie wykonało zbyt wielu działań.

Funkcja setInterval wykonuje wywołanie zwrotne w odniesieniu do interwału, dlatego z dużym prawdopodobieństwem przeszkodzi w interakcjach. Dzieje się tak dlatego, że w przeciwieństwie do pojedynczego wystąpienia wywołania setTimeout, które jest jednorazowym wywołaniem zwrotnym, które może przeszkodzić w interakcji z użytkownikiem, powtarzający się charakter usługi setInterval powoduje o wiele większe prawdopodobieństwo, że przejdzie na interakcję, co zwiększa opóźnienie danych wejściowych interakcji.

Zrzut ekranu z narzędziem profilowania wydajności w Narzędziach deweloperskich w Chrome przedstawiającym opóźnienie w danych wejściowych. Zadanie wywoływane przez funkcję licznika czasu ma miejsce tuż przed zainicjowaniem przez użytkownika interakcji polegającej na kliknięciu. Licznik czasu wydłuża jednak opóźnienie danych wejściowych, przez co wywołania zwrotne zdarzeń interakcji są uruchamiane później niż zwykle.
Minutnik zarejestrowany przez poprzednie wywołanie setInterval, który przyczynił się do opóźnienia danych wejściowych, zgodnie z panelem wydajności w Narzędziach deweloperskich w Chrome. Dodatkowe opóźnienie danych wejściowych powoduje, że wywołania zwrotne zdarzenia dla interakcji są wykonywane później niż jest to możliwe.

Jeśli liczniki czasu występują w kodzie własnym, masz nad nimi kontrolę. Oceń, czy ich potrzebujesz, lub postaraj się ograniczyć nakład pracy, jaki się zajmują. Liczniki czasu w skryptach innych firm to jednak inna historia. Często nie masz kontroli nad działaniem skryptu innej firmy, a rozwiązywanie problemów z wydajnością kodu zewnętrznego często wymaga współpracy z zainteresowanymi osobami w celu ustalenia, czy dany skrypt jest potrzebny. W takim przypadku skontaktuj się z zewnętrznym dostawcą skryptów, by ustalić, co może zrobić, by rozwiązać problemy z wydajnością witryny, które może on spowodować.

Unikaj długich zadań

Jednym ze sposobów na ograniczenie długich opóźnień wejściowych jest unikanie długich zadań. Gdy masz za dużo pracy w wątku głównym, która blokuje wątek główny podczas interakcji, zwiększa to opóźnienie wejściowe, zanim długie zadania zdążą zakończyć się do końca.

Wizualizacja pokazująca, jak długo zadania przedłużają opóźnienie danych wejściowych. U góry interakcja ma miejsce wkrótce po uruchomieniu pojedynczego długiego zadania, co powoduje znaczne opóźnienie danych wejściowych, przez co wywołania zwrotne zdarzeń są wykonywane znacznie później niż powinny. Na dole interakcja zachodzi mniej więcej w tym samym czasie, ale długie zadanie jest podzielone na kilka mniejszych w celu uzyskania zysku, dzięki czemu wywołania zwrotne zdarzeń interakcji są wykonywane znacznie szybciej.
Wizualizacja tego, co dzieje się z interakcjami, gdy zadania są zbyt długie i przeglądarka nie reaguje wystarczająco szybko na interakcje, oraz gdy dłuższe zadania są dzielone na mniejsze.

Poza ograniczeniem pracy nad zadaniem – i zawsze staraj się wykonywać jak najmniej pracy w wątku głównym – możesz poprawić reakcję na dane wejściowe użytkownika, rozdzielając długie zadania.

Pamiętaj o pokrywaniu się interakcji

Szczególnie trudne do optymalizacji wartości INP może być sytuacja, gdy Twoje interakcje się pokrywają. Pokrywanie się interakcji oznacza, że po interakcji z jednym elementem musisz wykonać kolejną interakcję ze stroną, zanim pierwsza interakcja z niej wyrenderuje kolejną klatkę.

Ilustracja pokazująca, kiedy zadania mogą się nakładać, co powoduje duże opóźnienia danych wejściowych. Na tym przykładzie interakcja związana z kliknięciem nakłada się na interakcję z klawiszem, aby zwiększyć opóźnienie danych wejściowych dla tej interakcji.
Wizualizacja 2 równoczesnych interakcji w narzędziu do profilowania wydajności w Narzędziach deweloperskich w Chrome. Renderowanie podczas początkowej interakcji z kliknięciem powoduje opóźnienie w przypadku kolejnych interakcji z klawiaturą.

Źródła interakcji mogą być równie proste jak powstawanie wielu interakcji w krótkim czasie. Może się tak zdarzyć, gdy użytkownicy wpisują tekst w polach formularza, gdzie w krótkim czasie może nastąpić wiele interakcji z klawiaturą. Jeśli praca nad kluczowym zdarzeniem jest szczególnie kosztowna (jak w typowym przypadku autouzupełniania pól, w których żądania sieciowe są wysyłane do backendu), masz kilka możliwości:

  • Rozważ odbijanie danych wejściowych, aby ograniczyć liczbę wywołań zwrotnych zdarzenia w danym okresie.
  • Używaj AbortController do anulowania wychodzących żądań fetch, aby wątek główny nie był przeciążony podczas obsługi wywołań zwrotnych fetch. Uwaga: do zatrzymywania zdarzeń można też używać właściwości signal instancji AbortController.

Innym źródłem zwiększonego opóźnienia danych wejściowych z powodu nakładających się interakcji mogą być drogie animacje. W szczególności animacje w języku JavaScript mogą uruchamiać wiele wywołań requestAnimationFrame, które mogą przeszkadzać w interakcjach użytkownika. Aby obejść ten problem, w miarę możliwości używaj animacji CSS i unikaj dodawania do kolejki potencjalnie kosztownych klatek animacji. Jeśli jednak to zrobisz, unikaj nieskomponowanych animacji, tak by były one uruchamiane głównie w wątkach GPU i kompozytorium, a nie w wątku głównym.

Podsumowanie

Opóźnienia danych wejściowych mogą nie odpowiadać większość czasu potrzebnego na interakcję, ale trzeba pamiętać, że każdy etap interakcji zajmuje czas, który można skrócić. Jeśli obserwujesz długie opóźnienie danych wejściowych, możesz spróbować je zmniejszyć. Unikanie cyklicznych wywołań licznika czasu, dzielenie długich zadań i świadomość potencjalnego nakładania się interakcji może pomóc w zminimalizowaniu opóźnienia danych wejściowych i spowodować szybszą interaktywność użytkowników witryny.

Baner powitalny z filmu Unsplash, Erik Mclean.