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. W tym wpisie omawiamy sposób, w jaki przeglądarka wyświetla stronę internetową. W poprzednim poście omawialiśmy proces renderowania i 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

Słysząc „zdarzenia wejścia”, możesz myśleć tylko o pisaniu w polu tekstowym lub klikaniu myszką, ale z punktu widzenia przeglądarki dane wejściowe to każdy 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 jako pierwszy otrzyma 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 renderera obsługuje zdarzenie odpowiednio, znajdując jego cel i uruchamiając przypisane do niego 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 tworzenie nowego kadru bez oczekiwania na wątek główny.

ograniczony obszar, który nie można szybko przewijać
Rysunek 3. Diagram opisujący dane wejściowe w przypadku 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ń. Ponieważ zdarzenia są przekazywane dalej, możesz dołączyć 1 obsługę zdarzenia 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();
    }
});

Ponieważ w przypadku wszystkich elementów wystarczy napisać tylko 1 obsługę zdarzenia, ergonomia tego wzorca delegowania 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 wprowadzanie danych do obszaru, który nie można szybko przewijać i który obejmuje całą stronę

Aby temu zapobiec, możesz przekazać opcje passive: true do metody Listener. 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,
Ryc. 5. 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ąć, zanim passive: true zostanie użyta do ograniczenia kierunku przewijania.preventDefault 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 malowania z pytaniem, co jest narysowane w punkcie x.y

Gdy wątek kompozytora wysyła zdarzenie wejściowe 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 większą rozdzielczość niż nasz 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ć nadmierne wywoływanie wątku głównego, Chrome łączy ciągłe zdarzenia (takie jak wheel, mousewheel, mousemove, pointermove, touchmove) i opóźnia ich wysyłanie do momentu tuż przed następnym requestAnimationFrame.

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

Wszystkie zdarzenia dyskretne, takie jak keydown, keyup, mouseup, mousedown, touchstarttouchend, 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 dobre wrażenia. Jeśli jednak tworzysz np. aplikację do rysowania i umieszczasz ścieżkę na podstawie współrzędnych touchmove, możesz utracić pośrednie współrzędne, co uniemożliwi narysowanie płynnej linii. W takim przypadku możesz użyć metody getCoalescedEvents w zdarzeniu wskazywania, aby uzyskać informacje o tych scalonych zdarzeniach.

getCoalescedEvents
Ryc. 9. Ścieżka płynnego gestu dotykowego po lewej stronie, złączona 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ś/zastanawiałaś się, dlaczego DevTools zaleca dodanie atrybutu {passive: true} do metody obsługi zdarzenia lub dlaczego warto umieścić atrybut async w 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 Twój kod był przyjazny dla przeglądarki, ale nie wiesz, od czego zacząć, Lighthouse to narzędzie, które przeprowadza audyt każdej witryny i wygeneruje raport o tym, co jest zrobione dobrze, a co wymaga poprawy. Przeglądanie listy kontroli daje też ogólne pojęcie o tym, na czym polegają problemy z przeglądarką.

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ół Chrome DevTools przygotował kilka samouczków dotyczących pomiaru 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 Twoja aplikacja nigdy nie zablokuje analizowania, możesz uruchomić ją z zasadami dotyczącymi skryptów asynchronicznych. Gdy włączysz sync-script: 'none', uniemożliwisz wykonywanie kodu JavaScript blokującego parsowanie. Dzięki temu żaden kod nie zablokuje parsowania, a przeglądarka nie musi się martwić o wstrzymanie 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. Dbanie o wygodę przeglądarki poprzez uporządkowanie kodu powoduje, że użytkownicy mają lepsze wrażenia. 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.

Czy 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.