Wyrównane zdarzenia wejściowe

Dave Tapuska
Dave Tapuska

TL;DR

  • Chrome 60 zmniejsza częstotliwość zdarzeń i poprawia spójność synchronizacji klatek.
  • Wprowadzona w Chrome 58 metoda getCoalescedEvents() zapewnia dostęp do tego samego bogactwa informacji o zdarzeniach co do tej pory.

Zapewnienie wygody użytkownikom internetu jest bardzo ważne. Czas między otrzymaniem zdarzenia wejściowego a faktycznym zaktualizowaniem elementów wizualnych jest ważny. Zwykle ważne jest też mniej wysiłku. W kilku ostatnich wersjach Chrome zmniejszyliśmy opóźnienia wejścia na tych urządzeniach.

Aby zapewnić płynność i wydajność, wprowadzamy w Chrome 60 zmianę, która sprawia, że te zdarzenia będą występować rzadziej, a jednocześnie zwiększyć szczegółowość dostarczanych informacji. Tak jak w przypadku premiery Jelly Bean i wprowadzenia Choreografa, który dopasowuje dane wejściowe na Androidzie, na wszystkich platformach wprowadzamy spójną ramkę w przeglądarce.

Czasami jednak potrzebujesz więcej zdarzeń. Dlatego w Chrome 58 wdrożyliśmy metodę o nazwie getCoalescedEvents(), która umożliwia aplikacji pobieranie pełnej ścieżki wskaźnika nawet wtedy, gdy odbiera mniej zdarzeń.

Najpierw porozmawiajmy o częstotliwości wydarzeń.

Zmniejszam częstotliwość zdarzeń

Zacznijmy od podstaw: ekrany dotykowe przekazują dane z częstotliwością 60–120 Hz, a myszy – zwykle z częstotliwością 100 Hz (ale w każdym miejscu do 2000 Hz). Jednak typowa częstotliwość odświeżania monitora to 60 Hz. Co to właściwie oznacza? Oznacza to, że otrzymujemy dane wejściowe częściej niż aktualizujemy wyświetlacz. Spójrzmy więc na osi czasu w narzędziach deweloperskich prostej aplikacji do malowania obrazów na płótnie.

Na ilustracji poniżej, przy wyłączonym wejściu wyrównanym do requestAnimationFrame(), widać wiele bloków przetwarzania na klatkę z niespójnym czasem renderowania. Małe żółte bloki wskazują testy trafień takich jak cel zdarzenia DOM, wysyłanie zdarzenia, uruchamianie JavaScriptu, aktualizowanie wyświetlanego węzła i ewentualnie ponowne obliczanie układu i stylów.

Oś czasu wydajności przedstawiająca niespójny czas renderowania klatek.

Dlaczego wprowadzamy dodatkowe zmiany, które nie powodują żadnych zmian wizualnych? Optymalnie nie chcemy wykonywać żadnych działań, które nie przynoszą korzyści użytkownikowi. Od wersji Chrome 60 potok wejściowy opóźni wysyłanie zdarzeń ciągłych (wheel, mousewheel, touchmove, pointermove, mousemove) i wysyła je tuż przed requestAnimationFrame() wywołaniem zwrotnym. Na obrazie poniżej (gdy ta funkcja jest włączona) widać bardziej spójny czas renderowania klatki i krótszy czas przetwarzania zdarzeń.

Przeprowadziliśmy eksperyment dotyczący tej funkcji w wersji Canary i Dev. Stwierdziliśmy, że przeprowadzamy o 35% mniej testów trafień, dzięki czemu główny wątek jest gotowy do częstszego uruchamiania.

Twórcy stron internetowych powinni pamiętać, że każde odrębne zdarzenia (takie jak keydown, keyup, mouseup, mousedown, touchstart, touchend) będą wysyłane od razu wraz ze zdarzeniami oczekującymi, z zachowaniem ich względnej kolejności. Po włączeniu tej funkcji znaczna część pracy jest wykonywana w zwykłym procesie pętli zdarzeń, co zapewnia spójny interwał wprowadzania. Dzięki temu zdarzenia ciągłe są wbudowane w zdarzenia scroll i resize, które zostały już uwzględnione w pętli zdarzeń w Chrome.

Oś czasu wydajności przedstawiająca stosunkowo spójny czas renderowania klatki.

Zauważyliśmy, że zdecydowana większość aplikacji korzystających z takich zdarzeń nie ma zastosowania dla większej częstotliwości. W Androidzie od wielu lat takie zdarzenia są już ujednolicone, więc nie ma tu nic nowego, ale w witrynach na komputerach mogą występować mniej szczegółowe zdarzenia. Od zawsze występował problem z zaburzonymi wątkami głównymi, które powodują problemy z płynnością wprowadzania danych. Oznacza to, że podczas działania aplikacji można zauważyć skoki w pozycji. Nie wiadomo, jak wskaźnik przeszedł z jednego miejsca na drugie.

Metoda getCoalescedEvents()

Jak powiedziałem, w rzadkich sytuacjach aplikacja wolała znać pełną ścieżkę wskaźnika. Aby rozwiązać problem dużych skoków i mniejszej częstotliwości zdarzeń, wprowadziliśmy w Chrome 58 rozszerzenie do zdarzeń związanych ze wskaźnikami o nazwie getCoalescedEvents(). Poniżej znajdziesz przykład tego, jak za pomocą tego interfejsu API użytkownik nie ma nic wspólnego z wątkiem głównym.

Porównanie zdarzeń standardowych i połączonych.

Zamiast otrzymywać tylko 1 zdarzenie, możesz uzyskać dostęp do tablicy zdarzeń historycznych, które je wywołały. Android, iOS i Windows mają bardzo podobne interfejsy API w natywnych pakietach SDK, a my udostępniamy podobny interfejs API w internecie.

Zwykle aplikacja do rysowania może narysować punkt, obserwując przesunięcia występujące w zdarzeniu:

window.addEventListener("pointermove", function(event) {
    drawPoint(event.pageX, event.pageY);
});

Ten kod można łatwo zmienić, aby używał tablicy zdarzeń:

window.addEventListener("pointermove", function(event) {
    var events = 'getCoalescedEvents' in event ? event.getCoalescedEvents() : [event];
    for (let e of events) {
    drawPoint(e.pageX, e.pageY);
    }
});

Pamiętaj, że nie każda właściwość w połączonych zdarzeniach jest wypełniona. W związku z tym, że zdarzenia łączące się z jazdą nie są tak naprawdę wysyłane, Niektóre pola, np. currentTarget i eventPhase, mają wartości domyślne. Wywołanie metod powiązanych z wysyłaniem, takich jak stopPropagation() lub preventDefault(), nie będzie miało wpływu na zdarzenie nadrzędne.