Optymalizuj opóźnienie wejściowe

Dowiedz się, czym jest opóźnienie wejścia, i poznaj techniki, które pozwolą je zmniejszyć, aby przyspieszyć interakcję.

Interakcje w internecie są złożone, a w przeglądarce zachodzą różne działania, które je wywołują. Wszystkie te zdarzenia łączy jednak to, że przed uruchomieniem wywołań zwrotnych zdarzeń występuje pewne opóźnienie wejściowe. Z tego przewodnika dowiesz się, czym jest opóźnienie wejściowe i co możesz zrobić, aby je zminimalizować i przyspieszyć interakcje w swojej witrynie.

Czym jest opóźnienie wejściowe?

Opóźnienie przy pierwszym działaniu to czas, jaki upływa od pierwszej interakcji użytkownika ze stroną (np. kliknięcia ekranu, kliknięcia myszą lub naciśnięcia klawisza) do momentu rozpoczęcia wykonywania funkcji zwrotnych zdarzenia. Każda interakcja zaczyna się od pewnego opóźnienia.

Uproszczona wizualizacja opóźnienia przy działaniu. Po lewej stronie znajduje się rysunek przedstawiający kursor myszy z rozbłyskiem w tle, co oznacza rozpoczęcie interakcji. Po prawej stronie znajduje się grafika przedstawiająca koło zębate, która symbolizuje moment rozpoczęcia działania procedur obsługi zdarzeń interakcji. Przestrzeń między nimi jest oznaczona jako opóźnienie wejściowe z nawiasem klamrowym.
Mechanizmy opóźnienia wejściowego. Gdy system operacyjny otrzyma dane wejściowe, musi przekazać je do przeglądarki, zanim rozpocznie się interakcja. Zajmuje to trochę czasu, który może się wydłużyć ze względu na istniejące zadania w głównym wątku.

Część opóźnienia wejściowego jest nieunikniona: system operacyjny zawsze potrzebuje trochę czasu, aby rozpoznać zdarzenie wejściowe i przekazać je do przeglądarki. Ta część opóźnienia wejściowego jest jednak często niezauważalna, a na samej stronie zachodzą inne procesy, które mogą powodować opóźnienia wejściowe na tyle długie, że stają się problemem.

Jak rozumieć opóźnienie wejściowe

Ogólnie rzecz biorąc, warto skrócić każdą część interakcji do minimum, aby witryna miała jak największe szanse na osiągnięcie wartości „dobrej” wskaźnika interakcji do kolejnego wyrenderowania (INP) niezależnie od urządzenia użytkownika. Kontrolowanie opóźnienia wejściowego to tylko jeden z elementów spełnienia tego progu.

Dlatego warto dążyć do jak najkrótszego opóźnienia wejściowego, aby osiągnąć próg „dobry” w przypadku INP. Pamiętaj jednak, że nie możesz całkowicie wyeliminować opóźnień we wprowadzaniu danych. Jeśli unikasz nadmiernego obciążenia wątku głównego, gdy użytkownicy próbują korzystać z Twojej strony, opóźnienie danych wejściowych powinno być wystarczająco małe, aby nie powodować problemów.

Jak zminimalizować opóźnienie wejściowe

Jak już wspomnieliśmy, pewne opóźnienie wejściowe jest nieuniknione, ale z drugiej strony pewne opóźnienie wejściowe jest do uniknięcia. Jeśli masz problemy z długimi opóźnieniami w reagowaniu na dane wejściowe, weź pod uwagę te kwestie.

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

W JavaScript są 2 często używane funkcje czasowe, które mogą przyczyniać się do opóźnienia danych wejściowych: setTimeoutsetInterval. Różnica między nimi polega na tym, że funkcja setTimeout planuje wywołanie zwrotne po upływie określonego czasu. setInterval z kolei planuje wywołanie zwrotne, które będzie wykonywane co n milisekund w nieskończoność lub do momentu zatrzymania timera za pomocą funkcji clearInterval.

setTimeout nie jest samo w sobie problematyczne – w rzeczywistości może być przydatne w unikaniu długich zadań. Zależy to jednak od tego, kiedy wystąpi limit czasu i czy użytkownik spróbuje wejść w interakcję ze stroną, gdy zostanie wywołane wywołanie zwrotne limitu czasu.

Dodatkowo funkcję setTimeout można uruchamiać w pętli lub rekurencyjnie, w którym to przypadku działa bardziej jak funkcja setInterval, ale najlepiej nie planować kolejnej iteracji, dopóki poprzednia się nie zakończy. Oznacza to, że pętla będzie za każdym razem ustępować miejsca głównemu wątkowi, gdy zostanie wywołana funkcja setTimeout. Należy jednak zadbać o to, aby wywołanie zwrotne nie wykonywało zbyt wielu czynności.

setInterval wywołuje funkcję zwrotną w określonych odstępach czasu, dlatego znacznie częściej utrudnia interakcje. Dzieje się tak, ponieważ w przeciwieństwie do pojedynczego wywołania funkcji setTimeout, które jest jednorazowym wywołaniem zwrotnym i może utrudniać interakcję użytkownika, powtarzający się charakter funkcji setInterval sprawia, że prawdopodobnie będzie ona utrudniać interakcję, a tym samym zwiększać opóźnienie wejściowe interakcji.

Zrzut ekranu profilera wydajności w Narzędziach deweloperskich w Chrome pokazujący opóźnienie wejścia. Zadanie uruchomione przez funkcję timera występuje tuż przed tym, jak użytkownik zainicjuje interakcję kliknięcia. Timer wydłuża jednak opóźnienie wejściowe, co powoduje, że wywołania zwrotne zdarzeń interakcji są wykonywane później niż w innych przypadkach.
Timer zarejestrowany przez poprzednie wywołanie setInterval, który przyczynia się do opóźnienia danych wejściowych, jak pokazano w panelu wydajności Narzędzi deweloperskich w Chrome. Dodane opóźnienie danych wejściowych powoduje, że wywołania zwrotne zdarzeń dla interakcji są wykonywane później niż w innych przypadkach.

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ć w nich pracę. Jednak liczniki w skryptach innych firm to zupełnie inna sprawa. Często nie masz kontroli nad tym, co robi skrypt firmy zewnętrznej. Rozwiązywanie problemów z wydajnością w kodzie firmy zewnętrznej często wymaga współpracy z zainteresowanymi stronami w celu ustalenia, czy dany skrypt firmy zewnętrznej jest niezbędny. Jeśli tak, skontaktuj się z dostawcą skryptu firmy zewnętrznej, aby ustalić, co można zrobić, aby rozwiązać problemy z wydajnością, które mogą występować w Twojej witrynie.

Unikaj długich zadań

Jednym ze sposobów na ograniczenie długich opóźnień w reakcji na dane wejściowe jest unikanie długich zadań. Jeśli masz nadmierną ilość pracy w wątku głównym, która blokuje go podczas interakcji, zwiększy to opóźnienie wejścia, zanim długie zadania zdążą się zakończyć.

Wizualizacja pokazująca, jak długie zadania wydłużają opóźnienie danych wejściowych. U góry widzimy, że interakcja następuje krótko po wykonaniu pojedynczego długiego zadania, co powoduje znaczne opóźnienie we wprowadzaniu danych i sprawia, że wywołania zwrotne zdarzeń są wykonywane znacznie później niż powinny. U podstaw interakcja występuje w mniej więcej tym samym czasie, ale długie zadanie jest dzielone na kilka mniejszych przez przekazywanie sterowania, 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 na nie reagować, w porównaniu z sytuacją, gdy dłuższe zadania są dzielone na mniejsze.

Oprócz minimalizowania ilości pracy wykonywanej w ramach zadania – a w wątku głównym zawsze należy dążyć do wykonywania jak najmniejszej ilości pracy – możesz poprawić szybkość reakcji na działania użytkownika, dzieląc długie zadania.

Zwracaj uwagę na nakładanie się interakcji

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

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

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

  • Rozważ odrzucanie danych wejściowych, aby ograniczyć liczbę wywołań zwrotnych zdarzeń w danym okresie.
  • Używaj 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 prerywania zdarzeń.

Innym źródłem zwiększonego opóźnienia we wprowadzaniu danych z powodu nakładających się interakcji mogą być kosztowne animacje. Animacje w JavaScript mogą generować wiele wywołań requestAnimationFrame, co może utrudniać interakcje użytkowników. Aby tego uniknąć, w miarę możliwości używaj animacji CSS, aby uniknąć kolejkowania potencjalnie kosztownych klatek animacji. Jeśli to zrobisz, unikaj nieskomponowanych animacji, aby animacje działały głównie na wątkach GPU i kompozytora, a nie na wątku głównym.

Podsumowanie

Opóźnienia we wprowadzaniu danych 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 we wprowadzaniu danych, możesz je skrócić. Unikanie powtarzających się wywołań zwrotnych timera, dzielenie długich zadań i świadomość potencjalnego nakładania się interakcji mogą pomóc w zmniejszeniu opóźnienia wejściowego, co przełoży się na szybsze interakcje dla użytkowników Twojej witryny.

Baner powitalny z Unsplash, autor: Erik Mclean.