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ść. Wszystkie mają jednak wspólną cechę: przed uruchomieniem wywołań zwrotnych zdarzeń występuje opóźnienie w dostarczonej informacji. Z tego przewodnika dowiesz się, czym jest opóźnienie w rejestracji danych wejściowych i jak je zminimalizować, aby interakcje z witryną przebiegały szybciej.

Opóźnienie przy pierwszym działaniu to okres od momentu, gdy użytkownik po raz pierwszy wchodzi w interakcję ze stroną (np. klika ekran, klika myszką lub naciska klawisz) do chwili, gdy zaczynają się wywołania zwrotne zdarzenia dotyczące interakcji. Każda interakcja rozpoczyna się z pewnym opóźnieniem.

Uproszczona wizualizacja opóźnienia sygnału wejściowego Po lewej stronie znajduje się rysunek odręczny kursora myszy z promiennikiem za nim, który oznacza początek interakcji. Po prawej stronie znajduje się rysunek liniowy koła zębatego, który wskazuje, kiedy uruchamiają się moduły obsługi zdarzeń interakcji. Przestrzeń między nimi jest oznaczona jako opóźnienie wejścia za pomocą nawiasu klamrowego.
Mechanika opóźnienia danych wejściowych. Gdy system operacyjny otrzyma dane wejściowe, musi je przekazać przeglądarce 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óźnienia 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żdą część interakcji należy skrócić do minimum, aby zwiększyć szanse witryny na osiągnięcie „dobrego” progu wartości danych wskaźnika Interakcja do kolejnego wyrenderowania (INP) niezależnie od urządzenia użytkownika. Kontrolowanie opóźnienia wprowadzania danych to tylko jeden z elementów, które pozwalają osiągnąć ten próg.

Dlatego warto dążyć do jak najkrótszego opóźnienia wejścia, aby osiągnąć „dobry” próg INP. Pamiętaj jednak, że nie można całkowicie wyeliminować opóźnień w składaniu danych. Jeśli unikniesz nadmiernego obciążania wątku głównego, gdy użytkownicy próbują wchodzić w interakcję ze stroną, opóźnienie danych wejściowych powinno być wystarczająco krótkie, aby 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ę zegarów, które uruchamiają nadmierne obciążenie wątku głównego.

W JavaScript istnieją 2 funkcje zegara, które mogą przyczyniać się do opóźnienia wprowadzania danych: setTimeoutsetInterval. 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 czasu, w jakim nastąpiło przekroczenie limitu czasu, oraz od tego, czy użytkownik próbuje wchodzić w interakcję ze stroną, gdy wykonywane jest wywołanie zwrotne po przekroczeniu limitu czasu.

Poza tym funkcja setTimeout może być uruchamiana w pętli lub rekurencyjnie, gdzie działa podobnie jak setInterval, lepiej jednak nie planować kolejnej iteracji, dopóki nie zostanie zakończona. Oznacza to, że za każdym razem, gdy wywoływana jest funkcja setTimeout, pętla zwraca kontrolę głównemu wątkowi. Pamiętaj jednak, aby wywołanie zwrotne tej funkcji nie wykonywało zbyt wielu operacji.

setInterval wywołuje wywołanie zwrotne w określonym odstępie czasu, przez co znacznie częściej może zakłócać interakcje. W odróżnieniu od pojedynczego wywołania funkcji setTimeout, które jest jednorazowym wywołaniem, które może przeszkadzać w interakcjach z użytkownikiem, setInterval jest wywoływane wielokrotnie, co znacznie zwiększa prawdopodobieństwo, że będzie przeszkadzać w interakcjach, zwiększając tym samym opóźnienie wprowadzania danych.

Zrzut ekranu przedstawiający profil wydajności w Narzędziach deweloperskich w Chrome, który pokazuje opóźnienie wprowadzania danych. Zadanie wywołane przez funkcję timera występuje tuż przed tym, jak użytkownik rozpocznie interakcję polegającą na kliknięciu. Jednak zegar wydłuża opóźnienie wprowadzania danych, przez co wywołania zwrotne zdarzenia interakcji są wykonywane później niż normalnie.
Timer zarejestrowany przez poprzedni wywołanie setInterval, który przyczynia się do opóźnienia wprowadzania danych, jak pokazano w panelu 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 zegary występują w kodzie własnym, masz nad nimi kontrolę. Zastanów się, czy ich potrzebujesz, i spróbuj zminimalizować ich liczbę. Jednak liczniki czasu w skryptach innych firm to zupełnie inna historia. Często nie masz kontroli nad tym, co robi skrypt zewnętrzny. Rozwiązywanie problemów z wydajnością kodu zewnętrznego często wymaga współpracy z osobami zainteresowanymi, aby ustalić, czy dany skrypt zewnętrzny jest niezbędny. Jeśli tak, skontaktuj się z dostawcą skryptu, aby dowiedzieć się, jak rozwiązać problemy z wydajnością, które mogą być przez niego powodowane.

Unikaj długich zadań

Jednym ze sposobów na zmniejszenie opóźnień w przyjmowaniu danych jest unikanie długich zadań. Jeśli wątek główny jest zbyt obciążony i blokuje interakcje, to spowoduje to opóźnienie wprowadzania danych, zanim długie zadania zostaną ukończone.

Wizualizacja, jak długo zadania wydłużają opóźnienie w wejściu. U góry interakcja występuje tuż po wykonaniu pojedynczego długiego zadania, co powoduje znaczne opóźnienie wprowadzania danych, które powoduje, że 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, a przeglądarka nie reaguje wystarczająco szybko na interakcje, a dłuższe zadania są dzielone na mniejsze zadania.

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

Pamiętaj o pokrywaniu się interakcji

Szczególnie trudne może być zoptymalizowanie INP, jeśli masz interakcje, które się na siebie nakładają. Nakładanie się interakcji oznacza, że po interakcji z jednym elementem wchodzisz w interakcję z innym elementem na stronie, zanim pierwotna interakcja zdąży wyrenderować kolejny kadr.

Przykład, kiedy zadania mogą się nakładać, powodując długie opóźnienia w wejściu. 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 jednoczesnych interakcji w profilu wydajności w Narzędziach deweloperskich w Chrome. Prace związane z renderowaniem podczas początkowej interakcji z kliknięciem powodują opóźnienie wprowadzania danych podczas następnej interakcji z klawiaturą.

Źródła nakładających się 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ą 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 sieci do zaplecza, masz kilka opcji:

  • Rozważ debounce danych wejściowych, aby ograniczyć liczbę wywołań funkcji zwrotnej zdarzenia w danym przedziale czasu.
  • 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: właściwości signal instancji AbortController można też używać do przerywania zdarzeń.

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 tego uniknąć, używaj animacji CSS, gdy tylko to możliwe, aby uniknąć kolejkowania potencjalnie kosztownych klatek animacji. Pamiętaj jednak, aby nie używać nieskomponowanych animacji, ponieważ animacje powinny być renderowane głównie na wątkach procesora graficznego i składania, a nie na 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 zauważysz długi czas reakcji na dane wejściowe, możesz go skrócić. Unikanie powtarzających się wywołań przez timer, dzielenie długich zadań i świadomość potencjalnego nakładania się interakcji mogą pomóc w zmniejszeniu opóźnienia wprowadzania danych, co prowadzi do szybszej interakcji z użytkownikami witryny.

Baner powitalny z filmu Unsplash, Erik Mclean.