Optymalizuj opóźnienie wejściowe

Dowiedz się, czym jest opóźnienie danych wejściowych, i poznaj techniki jego zmniejszania, aby przyspieszyć interaktywność.

Interakcje w internecie są skomplikowane, ponieważ w przeglądarce zachodzą różne rodzaje aktywności, które je napędzają. Wszystkie te elementy mają jednak wspólną cechę: powodują pewne opóźnienie wejściowe, zanim zaczną działać wywołania zwrotne zdarzeń. Z tego przewodnika dowiesz się, czym jest opóźnienie wejściowe i co możesz zrobić, aby je zminimalizować i przyspieszyć interakcje w witrynie.

Czym jest opóźnienie wejściowe?

Opóźnienie przy działaniu to okres od pierwszej interakcji użytkownika ze stroną (np. kliknięcia ekranu, kliknięcia myszą lub naciśnięcia klawisza) do momentu, gdy zaczynają działać wywołania zwrotne zdarzenia związane z interakcją. Każda interakcja zaczyna się od pewnego opóźnienia danych wejściowych.

Uproszczona wizualizacja opóźnienia wejściowego. Po lewej stronie znajduje się rysunek kursora myszy z błyskiem w tle, który symbolizuje początek interakcji. Po prawej stronie znajduje się rysunek koła zębatego, który oznacza moment rozpoczęcia działania modułów obsługi zdarzeń w przypadku interakcji. Przestrzeń między nimi jest oznaczona jako opóźnienie danych wejściowych za pomocą nawiasu klamrowego.
Mechanizmy opóźnienia przy pierwszym działaniu. Gdy system operacyjny otrzyma dane wejściowe, musi je przekazać do przeglądarki, zanim rozpocznie się interakcja. Zajmuje to trochę czasu, który może się wydłużyć, jeśli w głównym wątku są już wykonywane jakieś zadania.

Pewna część opóźnienia wejściowego jest nieunikniona: system operacyjny zawsze potrzebuje trochę czasu na rozpoznanie zdarzenia wejściowego i przekazanie go do przeglądarki. Jednak ta część opóźnienia wejściowego jest często niezauważalna, a na samej stronie dzieją się inne rzeczy, które mogą wydłużyć opóźnienia wejściowe na tyle, że spowodują problemy.

Jak interpretować opóźnienie wejściowe

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” progu wskaźnika interakcji do kolejnego wyrenderowania (INP), niezależnie od urządzenia użytkownika. Utrzymanie opóźnienia wejściowego pod kontrolą to tylko jeden z elementów, które pozwalają osiągnąć ten próg.

Dlatego, aby osiągnąć „dobry” próg INP, należy dążyć do jak najkrótszego opóźnienia wejściowego. Pamiętaj jednak, że nie da się całkowicie wyeliminować opóźnień wejściowych. Jeśli unikasz nadmiernego obciążenia wątku głównego, gdy użytkownicy próbują wejść w interakcję ze stroną, opóźnienie przy wprowadzaniu powinno być na tyle niskie, aby nie powodowało problemów.

Jak zminimalizować opóźnienie wejściowe

Jak już wspomnieliśmy, pewnego opóźnienia wejściowego nie da się uniknąć, ale z drugiej strony niektóre opóźnienia wejściowe można wyeliminować. Jeśli masz problemy z długimi opóźnieniami, weź pod uwagę te kwestie.

Unikaj powtarzających się timerów, które powodują nadmierne obciążenie wątku głównego.

W JavaScripcie są 2 często używane funkcje licznika czasu, które mogą przyczyniać się do opóźnienia przy wejściu: setTimeout i setInterval. Różnica między nimi polega na tym, że setTimeout planuje uruchomienie wywołania zwrotnego po określonym czasie. Z kolei setInterval planuje wywołanie zwrotne, które będzie uruchamiane co n milisekund bezterminowo lub do momentu zatrzymania licznika za pomocą clearInterval.

setTimeout nie jest problematyczne samo w sobie – może być pomocne 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ą, gdy zostanie uruchomione wywołanie zwrotne po przekroczeniu limitu czasu.

Dodatkowo setTimeout można uruchamiać w pętli lub rekurencyjnie, co działa podobnie do setInterval, ale najlepiej nie planować kolejnej iteracji, dopóki nie zakończy się poprzednia. Oznacza to, że pętla będzie przekazywać sterowanie do wątku głównego za każdym razem, gdy wywoływana jest funkcja setTimeout, ale musisz zadbać o to, aby jej wywołanie zwrotne nie wykonywało zbyt wielu zadań.

setInterval uruchamia wywołanie zwrotne w interwale, dlatego jest znacznie bardziej prawdopodobne, że będzie przeszkadzać w interakcjach. Dzieje się tak, ponieważ – w przeciwieństwie do pojedynczego wywołania setTimeout, które jest jednorazowym wywołaniem zwrotnym, które może zakłócić interakcję użytkownika – cykliczny charakter setInterval sprawia, że będzie ono zakłócać interakcję, co zwiększy opóźnienie interakcji.

Zrzut ekranu profilera wydajności w Narzędziach deweloperskich w Chrome pokazujący opóźnienie wejściowe. Zadanie uruchamiane przez funkcję licznika czasu występuje tuż przed rozpoczęciem przez użytkownika interakcji polegającej na kliknięciu. Licznik czasu wydłuża jednak opóźnienie wejściowe, co powoduje, że wywołania zwrotne zdarzeń interakcji są uruchamiane później, niż miałyby miejsce w innym przypadku.
Zegar zarejestrowany przez poprzednie wywołanie setInterval, który przyczynia się do opóźnienia wejściowego, jak pokazano w panelu wydajności Narzędzi deweloperskich w Chrome. Dodatkowe opóźnienie danych wejściowych powoduje, że wywołania zwrotne zdarzeń interakcji są uruchamiane później, niż mogłyby być.

Jeśli liczniki czasu występują w kodzie własnym, masz nad nimi kontrolę. Zastanów się, czy są Ci potrzebne, lub postaraj się jak najbardziej ograniczyć ich działanie. Inaczej jest jednak w przypadku liczników czasu w skryptach innych firm. Często nie masz kontroli nad tym, co robi skrypt innej firmy, a rozwiązanie problemów z wydajnością w kodzie innej firmy często wymaga współpracy z interesariuszami w celu ustalenia, czy dany skrypt innej firmy jest konieczny. Jeśli tak, musisz skontaktować się z dostawcą skryptu, aby ustalić, co można zrobić, aby rozwiązać problemy z wydajnością, które może on powodować w Twojej witrynie.

Unikaj długich zadań

Jednym ze sposobów na ograniczenie długich opóźnień wejściowych jest unikanie długich zadań. Jeśli wątek główny jest nadmiernie obciążony i blokowany podczas interakcji, opóźnienie działania będzie się zwiększać, zanim długie zadania zostaną ukończone.

Wizualizacja pokazująca, jak długie zadania wydłużają opóźnienie wejściowe. U góry interakcja następuje krótko po uruchomieniu pojedynczego długiego zadania, co powoduje znaczne opóźnienie wejściowe, które sprawia, że wywołania zwrotne zdarzeń są uruchamiane znacznie później, niż powinny. Na dole interakcja występuje mniej więcej w tym samym czasie, ale długie zadanie jest podzielone na kilka mniejszych, co pozwala na znacznie szybsze uruchamianie wywołań zwrotnych zdarzeń interakcji.
Wizualizacja tego, co dzieje się z interakcjami, gdy zadania są zbyt długie i przeglądarka nie może wystarczająco szybko reagować na interakcje, w porównaniu z sytuacją, gdy dłuższe zadania są dzielone na mniejsze.

Oprócz minimalizowania ilości pracy wykonywanej w ramach zadania (zawsze staraj się wykonywać jak najmniej pracy w głównym wątku) możesz poprawić czas reakcji na dane wejściowe użytkownika, dzieląc długie zadania.

Zwracaj uwagę na nakładanie się interakcji

Szczególnie trudnym elementem optymalizacji INP mogą być nakładające się interakcje. Nakładanie się interakcji oznacza, że po wejściu w interakcję z jednym elementem wykonujesz kolejną interakcję ze stroną, zanim początkowa interakcja zdąży wyrenderować następną klatkę.

Ilustracja pokazująca, kiedy zadania mogą się nakładać, powodując długie opóźnienia wejściowe. Na tym rysunku interakcja kliknięcia nakłada się na interakcję naciśnięcia klawisza, co zwiększa opóźnienie wejściowe interakcji naciśnięcia klawisza.
Wizualizacja 2 jednoczesnych interakcji w profilerze wydajności w Narzędziach deweloperskich w Chrome. Renderowanie podczas pierwszej interakcji kliknięcia powoduje opóźnienie wejściowe w przypadku kolejnej interakcji z klawiaturą.

Źródłem nakładania się interakcji może być po prostu to, że użytkownicy wykonują wiele interakcji w krótkim czasie. Może się to zdarzyć, gdy użytkownicy wpisują tekst w polach formularza, gdzie w bardzo krótkim czasie może wystąpić wiele interakcji z klawiaturą. Jeśli praca nad kluczowym zdarzeniem jest szczególnie kosztowna – jak w częstym przypadku pól autouzupełniania, w których żądania sieciowe są wysyłane do backendu – masz kilka opcji:

  • Rozważ eliminację drgań danych wejściowych, aby ograniczyć liczbę wywołań zwrotnych zdarzenia w danym okresie.
  • Użyj AbortController, aby anulować wychodzące żądania fetch, dzięki czemu wątek główny nie będzie przeciążony obsługą wywołań zwrotnych fetch. Uwaga: właściwość signal instancji AbortController może też służyć do przerywania zdarzeń.

Innym źródłem zwiększonego opóźnienia wejściowego z powodu nakładających się interakcji mogą być kosztowne animacje. W szczególności animacje w języku JavaScript mogą wywoływać wiele wywołań requestAnimationFrame, co może utrudniać interakcje użytkownika. Aby tego uniknąć, używaj animacji CSS, gdy tylko jest to możliwe, aby uniknąć kolejkowania potencjalnie kosztownych klatek animacji. Jeśli jednak to zrobisz, unikaj nieskomponowanych animacji, aby animacje były uruchamiane głównie w wątkach GPU i kompozytora, a nie w głównym wątku.

Podsumowanie

Opóźnienia wejściowe mogą nie stanowić większości czasu potrzebnego na wykonanie interakcji, ale warto pamiętać, że każda część interakcji zajmuje pewien czas, który można skrócić. Jeśli obserwujesz długie opóźnienie wejściowe, możesz je zmniejszyć. Unikanie cyklicznych wywołań zwrotnych, dzielenie długich zadań i świadomość potencjalnego nakładania się interakcji mogą pomóc w zmniejszeniu opóźnienia przy wejściu, co przyspieszy interaktywność witryny dla użytkowników.