Wyrównane zdarzenia wejściowe

Dave Tapuska
Dave Tapuska

TL;DR

  • Chrome 60 zmniejsza zacięcia, zmniejszając częstotliwość zdarzeń, co poprawia spójność czasu wyświetlania klatek.
  • Metoda getCoalescedEvents(), wprowadzona w Chrome 58, zapewnia taką samą ilość informacji o zdarzeniach, jaką masz od zawsze.

Ważne jest, aby zapewnić użytkownikom płynne działanie witryny. Czas między otrzymaniem zdarzenia wejściowego a aktualizacją wizualizacji jest ważny, a ogólna zasada polega na wykonywaniu jak najmniejszej ilości pracy. W ostatnich wersjach Chrome udało nam się zmniejszyć opóźnienie wprowadzania danych na tych urządzeniach.

W trosce o płynność i wydajność w Chrome 60 wprowadzamy zmianę, która powoduje, że te zdarzenia będą występować rzadziej, a jednocześnie zwiększy się szczegółowość dostarczanych informacji. Podobnie jak w przypadku wydania Jelly Bean, które wprowadziło Choreographer dopasowujący dane wejściowe na Androidzie, wprowadzamy dane wejściowe dopasowywane do klatek na potrzeby internetu na wszystkich platformach.

Czasami jednak potrzebujesz więcej zdarzeń. Dlatego w Chrome 58 wdrożyliśmy metodę getCoalescedEvents(), która pozwala aplikacji pobrać pełną ścieżkę wskaźnika, nawet jeśli otrzymuje ona mniej zdarzeń.

Najpierw porozmawiajmy o częstotliwości zdarzeń.

Zmniejszanie częstotliwości zdarzeń

Zacznijmy od podstaw: ekrany dotykowe wysyłają dane z częstotliwością 60–120 Hz, a myszki zwykle z częstotliwością 100 Hz (ale może to być dowolna wartość do 2000 Hz). Jednak typowa częstotliwość odświeżania monitora wynosi 60 Hz. Co to tak naprawdę oznacza? Oznacza to, że otrzymujemy dane z większą częstotliwością niż aktualizujemy wyświetlacz. Przyjrzyjmy się osi czasu wydajności z devtools w przypadku prostej aplikacji do tworzenia obrazów na płótnie.

Na poniższym obrazku, gdy wyłączono dopasowanie wejścia do requestAnimationFrame(), widać wiele bloków przetwarzania na kadr z niespójętnym czasem kadru. Żółte bloki wskazują testowanie trafień w przypadku takich elementów, jak cel zdarzenia DOM, wysyłanie zdarzenia, uruchamianie skryptu JavaScript, aktualizowanie węzła, nad którym się znajduje kursor, oraz ewentualne ponowne obliczanie układu i stylów.

Oś czasu skuteczności z niespójnym ustawieniem czasu klatek

Dlaczego więc wykonujemy dodatkową pracę, która nie powoduje żadnych zmian wizualnych? W idealnej sytuacji nie chcemy wykonywać żadnych działań, które nie przynoszą korzyści użytkownikowi. Począwszy od Chrome 60 potok danych będzie opóźniał wysyłanie ciągłych zdarzeń (wheel, mousewheel, touchmove, pointermove, mousemove) i wysyłać je tuż przed wywołaniem funkcji requestAnimationFrame() callback. Na obrazie poniżej (z włączoną funkcją) widać bardziej spójny czas klatek i mniej czasu poświęcanego na przetwarzanie zdarzeń.

Przeprowadziliśmy eksperyment z tą funkcją na kanałach Canary i Dev i zauważyliśmy, że wykonujemy o 35% mniej testów uderzeń, co pozwala wątkowi głównemu być gotowym do działania częściej.

Deweloperzy powinni pamiętać, że każde zdarzenie pojedyncze (np. keydown, keyup, mouseup, mousedown, touchstart, touchend) zostanie wysłane od razu wraz ze wszystkimi oczekującymi zdarzeniami, zachowując ich względną kolejność. Po włączeniu tej funkcji wiele zadań jest uproszczone do normalnego przepływu pętli zdarzeń, co zapewnia spójny interwał danych wejściowych. Dzięki temu zdarzenia ciągłe będą zgodne ze zdarzeniami scroll i resize, które zostały już uproszczone w ramach pętli zdarzeń w Chrome.

Oś czasu skuteczności pokazująca stosunkowo stały czas trwania klatek.

Zauważyliśmy, że zdecydowana większość aplikacji korzystających z tych zdarzeń nie potrzebuje wyższej częstotliwości. W Androidzie zdarzenia są już dopasowywane od kilku lat, więc nie ma tu nic nowego, ale na platformach komputerowych zdarzenia mogą być mniej szczegółowe. Zawsze istniał problem z niestabilnymi wątkami głównymi, które powodowały zakłócenia płynności wprowadzania danych. Oznacza to, że możesz zauważyć skoki pozycji, gdy aplikacja wykonuje działanie, co uniemożliwia ustalenie, jak wskaźnik przemieszcza się z jednego miejsca do drugiego.

Metoda getCoalescedEvents()

Jak już wspomniałem, w rzadkich przypadkach aplikacja może potrzebować pełnej ścieżki wskaźnika. Aby rozwiązać problem dużych skoków i zmniejszoną częstotliwość zdarzeń, w wersji Chrome 58 wprowadziliśmy rozszerzenie zdarzeń wskaźnika o nazwie getCoalescedEvents(). Poniżej znajdziesz przykład tego, jak za pomocą tego interfejsu API można ukryć z aplikacji drgania głównego wątku.

Porównywanie zdarzeń standardowych i złączonych

Zamiast pojedynczego zdarzenia możesz uzyskać dostęp do tablicy historycznych zdarzeń, które spowodowały to zdarzenie. Android, iOSWindows mają bardzo podobne interfejsy API w natywnym pakiecie SDK, a my udostępniamy podobny interfejs API w internecie.

Zwykle aplikacja do rysowania może narysować punkt, patrząc na przesunięcia 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 wszystkie właściwości w zjednoczonych zdarzeniach są wypełnione. Zdarzenia scalone nie są wysyłane, tylko przetwarzane, więc nie są testowane. Niektóre pola, takie jak currentTargeteventPhase, będą miały wartości domyślne. Wywoływanie metod związanych z rozsyłaniem, takich jak stopPropagation() lub preventDefault(), nie będzie miało wpływu na zdarzenie nadrzędne.