Nowoczesna przeglądarka internetowa (część 4)

Mariko Kosaka

Dane wejściowe docierają do kompozytora

To ostatni z 4 artykułów z cyklu, w którym przyjrzeliśmy się Chrome od środka, aby pokazać, jak przeglądarka obsługuje nasz kod podczas wyświetlania strony internetowej. W poprzednim poście przyglądaliśmy się procesowi renderowania i znajdowaliśmy kompozytor. W tym poście omówimy, jak kompozytor umożliwia płynną interakcję po wprowadzeniu danych przez użytkownika.

Zdarzenia wejściowe z perspektywy przeglądarki

Gdy słyszysz „zdarzenia wejściowe”, możesz myśleć tylko o wpisywaniu tekstu w polu tekstowym lub kliknięciu myszy, ale z perspektywy przeglądarki wprowadzanie tekstu oznacza dowolny gest użytkownika. Przewijanie kółkiem myszy to zdarzenie wprowadzania danych, a dotknięcie lub najechanie kursorem na element to też zdarzenie wprowadzania danych.

Gdy użytkownik wykona gest, np. dotknie ekranu, proces przeglądarki jest pierwszym, który odbiera gest. Proces przeglądarki wie jednak tylko, gdzie wystąpił ten gest, ponieważ treści na karcie są obsługiwane przez proces renderowania. Proces przeglądarki wysyła typ zdarzenia (np. touchstart) i jego współrzędne do procesu renderowania. Proces renderowania odpowiednio obsługuje zdarzenia, znajdując miejsce docelowe zdarzenia i uruchamiając dołączone detektory zdarzeń.

zdarzenie wejściowe
Rysunek 1. Zdarzenie wejścia przekazywane przez proces przeglądarki do procesu renderera

Kompozytor odbiera zdarzenia wejściowe

Rysunek 2. Widok w oknie przeglądarki nad warstwami strony

W poprzednim poście omawialiśmy, jak kompozytor może obsługiwać płynne przewijanie, łącząc zeskalowane warstwy. Jeśli do strony nie jest dołączony żaden odbiorca zdarzenia wejściowego, wątek kompozytora może utworzyć nową ramkę złożoną całkowicie niezależnie od wątku głównego. Co jednak, jeśli do strony zostały dołączone niektóre odbiorniki zdarzeń? Jak wątek kompozytora ma się dowiedzieć, czy zdarzenie wymaga obsługi?

Region z wolnym przewijaniem

Uruchamianie kodu JavaScript to zadanie głównego wątku, więc gdy strona jest złożona, wątek kompozytora oznacza region strony z dołączonymi obslugami zdarzeń jako „Obszar nieprzewijalny szybko”. Dzięki tym informacjom wątek kompozytora może wysłać zdarzenie wejściowe do głównego wątku, jeśli zdarzenie wystąpi w tym regionie. Jeśli zdarzenie wejściowe pochodzi spoza tego regionu, wątek kompozytora kontynuuje komponowanie nowej ramki, nie czekając na wątek główny.

ograniczony obszar, w którym nie można szybko przewijać
Rysunek 3. Schemat opisanego wejścia do regionu, który nie jest przewijany szybko

Podczas pisania modułów obsługi zdarzeń pamiętaj o następujących kwestiach:

Typowym wzorcem obsługi zdarzeń w programowaniu stron internetowych jest delegowanie zdarzeń. Zdarzenia są przekazywane dalej, więc możesz dołączyć jeden moduł obsługi zdarzeń do najwyższego elementu i przekazać zadania na podstawie celu zdarzenia. Możesz widzieć lub pisać kod podobny do tego:

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

Dla wszystkich elementów wystarczy utworzyć jeden moduł obsługi zdarzeń, więc ergonomia tego wzorca przekazywania zdarzeń jest atrakcyjna. Jeśli jednak spojrzysz na ten kod z perspektywy przeglądarki, zobaczysz, że cała strona jest oznaczona jako obszar, po którym nie można szybko przewijać. Oznacza to, że nawet jeśli Twoja aplikacja nie potrzebuje danych wejściowych z pewnych części strony, wątek kompozytora musi się komunikować z głównym wątkiem i czekać na niego za każdym razem, gdy pojawi się zdarzenie wejściowe. W ten sposób kompozytor nie będzie mógł płynnie przewijać.

Pełna strona bez możliwości szybkiego przewijania
Rysunek 4. Diagram opisujący dane wprowadzane w regionie, który nie jest przewijany szybko, obejmujący całą stronę

Aby temu zapobiec, możesz przekazać opcje passive: true do swojego odbiornika zdarzeń. To sugeruje przeglądarce, że nadal chcesz słuchać zdarzenia w głównym wątku, ale kompozytor może też skompilować nowy obraz.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

Sprawdź, czy można anulować zdarzenie

przewijanie strony;
Teoria: strona internetowa z częścią strony zablokowaną na przewijanie poziome

Wyobraź sobie, że na stronie masz pole, w którym chcesz ograniczyć kierunek przewijania tylko do poziomego.

Użycie opcji passive: true w zdarzeniu wskaźnika spowoduje, że przewijanie strony może być płynne, ale pionowe przewijanie może się rozpocząć w chwili, gdy chcesz użyć opcji preventDefault, aby ograniczyć kierunek przewijania. Możesz to sprawdzić, używając metody event.cancelable.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

Możesz też użyć reguły CSS, np. touch-action, aby całkowicie wyeliminować obciążenie.

#area {
  touch-action: pan-x;
}

Znajdowanie celu zdarzenia

test pozycji wskaźnika
Wątek główny: przeglądanie rekordów paint, które wskazują, co jest narysowane w punkcie x.y

Gdy wątek kompozytora wysyła zdarzenie wejścia do głównego wątku, najpierw wykonywany jest test uderzenia, aby znaleźć cel zdarzenia. Test uderzenia korzysta z danych rekordów malowania wygenerowanych w procesie renderowania, aby ustalić, co znajduje się pod współrzędnymi punktu, w którym wystąpiło zdarzenie.

Minimalizowanie wysyłania zdarzeń do wątku głównego

W poprzednim poście omawialiśmy, jak typowy wyświetlacz odświeża ekran 60 razy na sekundę i jak musimy dostosować się do tempa, aby zapewnić płynną animację. W przypadku urządzeń z ekranem dotykowym zdarzenie dotyku występuje 60–120 razy na sekundę, a w przypadku myszy – 100 razy na sekundę. Zdarzenie wejściowe ma wyższą dokładność, niż można odświeżyć ekran.

Jeśli ciągłe zdarzenie, np. touchmove, jest wysyłane do głównego wątku 120 razy na sekundę, może to spowodować nadmierną liczbę testów trafień i wykonania kodu JavaScript w porównaniu z wolnym odświeżaniem ekranu.

zdarzenia bez filtrowania
Ryc. 7. Zdarzenia zapełniające ramkę osi czasu, powodujące drżenie strony

Aby zminimalizować liczbę wywołań wątku głównego, Chrome łączy zdarzenia ciągłe (np. wheel, mousewheel, mousemove, pointermove, touchmove) i opóźnia wysyłanie wiadomości aż do momentu przed następnym requestAnimationFrame.

złączone zdarzenia
Figura 8. Ta sama oś czasu co wcześniej, ale zdarzenie jest scalone i opóźnione

Wszystkie konkretne zdarzenia, takie jak keydown, keyup, mouseup, mousedown, touchstart i touchend, są wysyłane natychmiast.

Użyj getCoalescedEvents, aby uzyskać zdarzenia wewnątrz ramki

W przypadku większości aplikacji internetowych wystarczy użycie złączonych zdarzeń, aby zapewnić użytkownikom wygodę. Jeśli jednak tworzysz takie elementy, jak aplikacja do rysowania i umieszczasz ścieżkę na podstawie współrzędnych touchmove, możesz utracić różnice między współrzędnymi w celu narysowania gładkiej linii. W takim przypadku możesz użyć metody getCoalescedEvents w zdarzeniu wskazywania, aby uzyskać informacje o tych scalonych zdarzeniach.

getCoalescedEvents
Rys. 9. Po lewej ścieżka płynnego dotyku, po prawej scalona ograniczona ścieżka po prawej
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

Dalsze kroki

W tej serii omawialiśmy działanie przeglądarki internetowej. Jeśli nigdy nie zastanawiałeś(-aś) się, dlaczego DevTools zaleca dodanie atrybutu {passive: true} do Twojego przetwarzacza zdarzeń lub dlaczego warto dodać atrybut async do tagu skryptu, mam nadzieję, że ta seria artykułów pomoże Ci zrozumieć, dlaczego przeglądarka potrzebuje tych informacji, aby zapewnić szybsze i płynniejsze działanie internetu.

Korzystanie z Lighthouse

Jeśli chcesz, aby kod był przyjazny dla przeglądarki, ale nie wiesz, od czego zacząć, Lighthouse to narzędzie, które przeprowadza audyt każdej witryny i wyświetla raport pokazujący, co zostało zrobione prawidłowo, a co wymaga ulepszenia. Jeśli czytasz listę audytów, zorientujesz się, na co zwraca uwagę przeglądarka.

Dowiedz się, jak mierzyć skuteczność

W zależności od witryny zmiany w jej wydajności mogą się różnić, dlatego ważne jest, aby mierzyć jej wydajność i decydowować, co jest dla niej najlepsze. Zespół Narzędzi deweloperskich w Chrome udostępnia kilka samouczków na temat mierzenia wydajności witryny.

Dodawanie polityki dotyczącej funkcji do witryny

Jeśli chcesz podjąć dodatkowy krok, Zasady dotyczące funkcji to nowa funkcja platformy internetowej, która może być dla Ciebie zabezpieczeniem podczas tworzenia projektu. Włączenie zasad dotyczących funkcji gwarantuje określone działanie aplikacji i zapobiega popełnianiu błędów. Jeśli na przykład chcesz mieć pewność, że aplikacja nigdy nie będzie blokować analizy, możesz uruchomić w niej zasadę skryptów synchronicznych. Gdy funkcja sync-script: 'none' jest włączona, nie można wykonać kodu JavaScript blokującego parser. Dzięki temu żaden kod nie będzie blokował parsowania, a przeglądarka nie musi się martwić o wstrzymywanie parsowania.

Podsumowanie

dziękuję

Gdy zaczynałam tworzyć strony internetowe, zależało mi tylko na tym, jak pisać kod i co może zwiększyć moją produktywność. Te kwestie są ważne, ale powinniśmy też zastanowić się, jak przeglądarka interpretuje nasz kod. Współczesne przeglądarki stale inwestują w rozwiązania, które mają zapewnić użytkownikom lepsze wrażenia z korzystania z Internetu. Uporządkowanie kodu w celu ułatwienia przeglądarce jego odczytania poprawi wrażenia użytkownika. Mam nadzieję, że dołączysz do mnie w questu polegającym na łagodnym traktowaniu przeglądarek.

Dziękujemy wszystkim, którzy sprawdzili wczesne wersje tej serii, w tym (ale nie tylko): Alexowi Russellowi, Paulowi Irishowi, Meggin Kearney, Ericowi Bidelmanowi, Mathiasowi Bynensowi, Addy Osmani, Kinuko Yasudzie, Nasko Oskovowi i Charliemu Reisowi.

Podobała Ci się ta seria? Jeśli masz pytania lub sugestie dotyczące przyszłego wpisu, daj nam znać w sekcji komentarzy poniżej lub na Twitterze, pisząc do @kosamari.