Optymalizuj opóźnienie wejściowe

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

Interakcje w internecie są złożone, a w przeglądarce zachodzi wiele różnych działań, 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 wywołań zwrotnych zdarzenia. Każda interakcja zaczyna się od pewnego opóźnienia.

Uproszczona wizualizacja opóźnienia wejściowego. 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 za pomocą nawiasu klamrowego.
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ć, jeśli w głównym wątku są wykonywane inne zadania.

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 progowej „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 danych wejściowych, 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ą wejść w interakcję ze stroną, 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 można uniknąć. Jeśli masz problemy z długimi opóźnieniami we wprowadzaniu danych, weź pod uwagę te kwestie.

Unikaj powtarzających się timerów, 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 uruchamiane 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ć pomocne w unikaniu długich zadań. Zależy to jednak od tego, kiedy wystąpi przekroczenie limitu czasu i czy użytkownik spróbuje wejść w interakcję ze stroną, gdy zostanie wywołane wywołanie zwrotne przekroczenia 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ć następnej iteracji, dopóki nie zakończy się poprzednia. Oznacza to, że pętla będzie za każdym razem przekazywać kontrolę do wątku głównego, gdy zostanie wywołana funkcja setTimeout. Należy jednak zadbać o to, aby funkcja zwrotna nie wykonywała 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. Wydłuża on 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 wejściowego, jak pokazano na panelu wydajności w Narzędziach deweloperskich w Chrome. Dodane opóźnienie wejścia 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 czasu 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 działania, 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: interakcja następuje krótko po wykonaniu pojedynczego długiego zadania, co powoduje znaczne opóźnienie wejściowe, w wyniku którego wywołania zwrotne zdarzeń są wykonywane znacznie później niż powinny. U dołu interakcja występuje mniej więcej w 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 dane wejściowe użytkownika, dzieląc długie zadania.

Zwróć uwagę na nakładanie się interakcji

Szczególnie trudnym aspektem optymalizacji INP może być sytuacja, w której masz 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 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 podczas kolejnej interakcji z klawiaturą.

Źródła nakładania się interakcji mogą być proste, np. użytkownicy wykonują wiele interakcji 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 do backendu wysyłane są żądania sieciowe, masz kilka opcji:

  • Rozważ eliminację drgań klawiszy 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. W szczególności animacje w JavaScript mogą wywoływać 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 danych wejściowych, 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 we wprowadzaniu danych, co przełoży się na szybsze interakcje użytkowników z Twoją witryną.

Baner powitalny z Unsplash, autor: Erik Mclean.