Optymalizuj opóźnienie wejściowe

Dowiedz się, czym jest opóźnienie sygnału wejściowego, i poznaj techniki jego redukcji, aby zwiększyć interaktywność.

Interakcje w internecie to złożone rzeczy, a każda aktywność w przeglądarce musi ich prowadzić. Łączy je jednak to, że następują pewne opóźnienia przed rozpoczęciem wywołań zwrotnych zdarzeń. Z tego przewodnika dowiesz się, czym jest opóźnienie wprowadzania danych i jak je zminimalizować, aby interakcje ze stroną działały szybciej.

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

Opóźnienie wejścia to czas od pierwszej interakcji użytkownika ze stroną – np. dotknięcia ekranu, kliknięcia myszą lub naciśnięcia klawisza – do momentu rozpoczęcia wywołania zwrotnego tej interakcji. Każda interakcja zaczyna się z pewnym opóźnieniem w związku z przesyłaniem danych.

Uproszczona wizualizacja opóźnienia danych wejściowych. Po lewej stronie znajduje się grafika liniowa kursora myszy z wystrzelonym gwiazdą, która symbolizuje początek interakcji. Po prawej znajduje się sztuka liniowa koła zębatego wskazująca, kiedy uruchamiają się moduły obsługi zdarzeń interakcji. Odstęp między nimi jest oznaczany jako opóźnienie danych wejściowych w nawiasie klamrowym.
Mechanika opóźnienia wejścia. System operacyjny odbierze dane wejściowe do przeglądarki przed rozpoczęciem interakcji. Zajmie to określony czas, który można zwiększyć przez wykonanie tej czynności w wątku głównym.

Pewnej części opóźnienia wprowadzania danych jest nieuniknione. System operacyjny potrzebuje trochę czasu, by rozpoznać zdarzenie wejściowe i przekazać je do przeglądarki. Jednak taka część opóźnienia wprowadzania danych jest często niezauważalna, a na stronie dzieją się też inne czynniki, które mogą powodować opóźnienia na tyle długie, że powodują problemy.

Co zrobić z opóźnieniem wprowadzania danych

Zasadniczo każda część interakcji powinna być jak najkrótsza, aby witryna miała jak największe szanse na osiągnięcie progu „dobrej” interakcji z interakcją do następnego wyrenderowania (INP), niezależnie od urządzenia użytkownika. Kontrolowanie opóźnienia danych wejściowych to tylko jeden z elementów osiągnięcia tego progu.

Dlatego należy dążyć do jak najkrótszego możliwego opóźnienia wejścia, aby osiągnąć próg „dobrej” wartości INP. Musisz jednak pamiętać, że nie można całkowicie wyeliminować opóźnień w przesyłaniu danych. Jeśli unikasz nadmiernej pracy w wątku głównym, gdy użytkownicy próbują korzystać z Twojej strony, opóźnienie wprowadzania powinno być na tyle małe, aby uniknąć problemów.

Jak zminimalizować opóźnienie sygnału

Jak wspomnieliśmy wcześniej, pewne opóźnienia wejściowe są nieuniknione, ale z drugiej strony można ich uniknąć. Oto kilka kwestii, które warto wziąć pod uwagę, jeśli zmagasz się z długimi opóźnieniami w przesyłaniu danych.

Unikaj powtarzających się minutników, które uruchamiają nadmierną pracę w wątku głównym

W JavaScripcie są 2 powszechnie używane funkcje licznika czasu, które mogą wpływać na opóźnienie danych wejściowych: setTimeout i setInterval. Różnica między nimi polega na tym, że setTimeout planuje wykonanie wywołania zwrotnego po określonym czasie. setInterval z kolei planuje wykonanie wywołania zwrotnego co n milisekund lub do momentu zatrzymania licznika za pomocą przycisku clearInterval.

Usługa setTimeout sama w sobie nie stanowi problemu, ale może być pomocna, aby unikać długich zadań. Zależy to jednak od czasu przekroczenia limitu czasu oraz od tego, czy użytkownik spróbuje wejść w interakcję ze stroną po upływie limitu czasu oczekiwania.

Dodatkowo setTimeout może być uruchamiany w pętli lub rekurencyjnie, gdzie działa bardziej jak setInterval. Lepiej jednak nie planować następnej iteracji do czasu zakończenia poprzedniej. Oznacza to, że pętla jest przekazywana w wątku głównym przy każdym wywołaniu funkcji setTimeout, jednak należy zadbać o to, aby wywołanie zwrotne nie zakończyło się zbyt dużą pracą.

Funkcja setInterval uruchamia wywołanie zwrotne w określonym przedziale czasu, dlatego jest znacznie bardziej prawdopodobne, że przeszkadza w interakcjach. Dzieje się tak, ponieważ w przeciwieństwie do pojedynczego wystąpienia wywołania setTimeout, które jest jednorazowym wywołaniem zwrotnym, które może przeszkodzić w interakcji użytkownika, cykliczny charakter setInterval sprawia, że znacznie zwiększa prawdopodobieństwo, że przeszka ono w interakcji, co zwiększy opóźnienie interakcji.

Zrzut ekranu przedstawiający narzędzie do profilowania wydajności w Narzędziach deweloperskich w Chrome przedstawiające opóźnienie wprowadzania. Zadanie wywoływane przez funkcję licznika czasu występuje tuż przed inicjowaniem interakcji polegającej na kliknięciu przez użytkownika. Licznik czasu wydłuża jednak opóźnienie wprowadzania danych, przez co wywołania zwrotne zdarzenia interakcji są wykonywane później niż zwykle.
Licznik zarejestrowany przez poprzednie wywołanie setInterval, który przyczynił się do opóźnienia wejścia, tak jak to widać na panelu wydajności Narzędzi deweloperskich w Chrome. Dodatkowe opóźnienie danych wejściowych powoduje, że wywołania zwrotne zdarzenia dla tej interakcji są wykonywane później, niż byłoby to możliwe.

Jeśli liczniki czasu działają w kodzie własnym, masz nad nimi kontrolę. Zastanów się, czy są Ci one potrzebne, czy też postaraj się je maksymalnie ograniczyć. Liczniki czasu w scenariuszach zewnętrznych to jednak coś innego. Często nie masz kontroli nad działaniem zewnętrznego skryptu, a rozwiązywanie problemów z wydajnością kodu zewnętrznego wymaga współpracy z zainteresowanymi osobami w celu określenia, czy dany skrypt zewnętrzny jest potrzebny. Jeśli tak, skontaktuj się z tym dostawcą, aby dowiedzieć się, jak rozwiązać problemy z wydajnością, jakie mogą powodować w witrynie.

Unikanie długich zadań

Jednym ze sposobów na ograniczenie długich opóźnień przy wprowadzaniu danych jest unikanie długich zadań. Jeśli nadmierna ilość pracy z wątku głównego blokuje ten wątek podczas interakcji, wydłuża się opóźnienie wprowadzania danych przed zakończeniem długich zadań.

Wizualizacja tego, jak długo zadania zwiększają 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 i sprawia, że wywołania zwrotne zdarzeń są wykonywane znacznie później niż powinny. Na dole interakcja ma miejsce mniej więcej w tym samym czasie, ale długie zadanie zostaje podzielone na kilka mniejszych, dzięki czemu wywołania zwrotne zdarzenia interakcji będą realizowane znacznie szybciej.
Wizualizacja tego, co dzieje się z interakcjami, gdy zadania są zbyt długie, a przeglądarka nie jest w stanie wystarczająco szybko reagować na interakcje, lub gdy dłuższe zadania są dzielone na mniejsze zadania.

Poza ograniczeniem ilości pracy wykonywanej w danym zadaniu i zawsze staraj się wykonywać jak najmniejszą ilość pracy w wątku głównym – możesz też poprawić reakcję na działania użytkownika przez podzielenie długich zadań.

Zwróć uwagę na pokrywanie się interakcji

Szczególnie trudne w optymalizacji INP może być sytuacja, w której interakcje się pokrywają. Nakładanie się interakcji oznacza, że po interakcji z jednym elementem rozpoczynasz kolejną interakcję ze stroną, zanim pierwsza interakcja miała szansę wyrenderować następną klatkę.

Ilustracja pokazująca, kiedy zadania mogą się pokrywać, co powoduje duże opóźnienia w dostarczaniu danych. Na tym przykładzie interakcja po kliknięciu nakłada się na interakcję z klawiszem, aby zwiększyć opóźnienie wprowadzania danych w przypadku interakcji z klawiszem.
Wizualizacja 2 równoczesnych interakcji w narzędziu do profilu wydajności dostępnym w Narzędziach deweloperskich w Chrome. Renderowanie podczas pierwszej interakcji z klawiaturą powoduje opóźnienie danych wejściowych w przypadku dalszych interakcji z klawiaturą.

Źródła 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ą pola formularza, czyli w krótkim czasie może dojść do wielu interakcji z klawiaturą. Jeśli praca nad kluczowym zdarzeniem jest szczególnie kosztowna – na przykład w przypadku pól autouzupełniania, w których żądania sieciowe są wysyłane do backendu, masz 2 możliwości:

  • Rozważ odrzucanie danych wejściowych, aby ograniczyć liczbę wywołań zwrotnych zdarzenia w danym okresie.
  • Użyj funkcji AbortController, aby anulować wychodzące żądania fetch, aby wątek główny nie przeciążył się podczas obsługi wywołań zwrotnych fetch. Uwaga: właściwości signal instancji AbortController może też służyć do przerywania zdarzeń.

Innym źródłem zwiększonego opóźnienia danych wejściowych z powodu nakładających się interakcji mogą być kosztowne animacje. Animacje w języku JavaScript mogą w szczególności powodować uruchamianie wielu wywołań requestAnimationFrame, które mogą zakłócać interakcje użytkowników. Aby obejść ten problem, w miarę możliwości używaj animacji CSS. Pozwoli to uniknąć umieszczania w kolejce potencjalnie kosztownych klatek animacji. Jeśli to zrobisz, unikaj nieskomponowanych animacji, aby działały one głównie w wątkach GPU i kompozytora, a nie w wątku głównym.

Podsumowanie

Opóźnienia danych wejściowych mogą nie odpowiadać większości czasu potrzebnego na przeprowadzenie interakcji, ale pamiętaj, że każda jej część zajmuje dużo czasu, który możesz skrócić. Jeśli obserwujesz duże opóźnienie wprowadzania danych, masz szansę je zmniejszyć. Unikanie powtarzających się wywołań zwrotnych licznika czasu, dzielenie długich zadań i świadomość o potencjalnym nakładaniu się interakcji może pomóc zmniejszyć opóźnienie wprowadzania danych i przyspieszyć interakcję użytkowników witryny.

Baner powitalny z albumu Unsplash, autorstwa Erika Mcleana.