Wewnętrzne działanie procesu renderowania
To jest część 3 z 4-częściowej serii na temat działania przeglądarek. Wcześniej omówiliśmy architekturę wieloprocesową i proces nawigacji. W tym poście przyjrzymy się, co dzieje się w mechanizmie renderowania.
Proces renderowania obejmuje wiele aspektów wydajności sieci. Ze względu na to, że w procesie renderowania wiele się dzieje, ten post jest tylko ogólnym omówieniem tego procesu. Więcej materiałów znajdziesz w sekcji Skuteczność w Podstawach tworzenia witryn.
Procesy mechanizmu renderowania obsługują treści z internetu
Proces renderowania odpowiada za wszystko, co dzieje się na karcie. W procesie renderowania wątek główny obsługuje większość kodu wysyłanego do użytkownika. Czasami części kodu JavaScript są obsługiwane przez wątki instancji roboczych, jeśli używasz mechanizmów roboczych witryn roboczych lub service worker. Wątki kompozytora i rastru są również uruchamiane w procesach renderowania, aby sprawnie i płynnie renderować stronę.
Podstawowym zadaniem procesu renderowania jest przekształcenie kodu HTML, CSS i JavaScript w stronę internetową, z którą użytkownik może wchodzić w interakcje.
Analiza
Konstrukcja DOM
Gdy mechanizm renderowania otrzyma komunikat o zatwierdzeniu na potrzeby nawigacji i zacznie otrzymywać dane HTML, wątek główny rozpoczyna analizowanie ciągu tekstowego (HTML) i przekształca go w konstruowanie (DOM).
DOM to wewnętrzna reprezentacja strony w przeglądarce oraz struktura danych i interfejs API, z którymi deweloper stron internetowych może wchodzić w interakcje za pomocą JavaScriptu.
Przetwarzanie dokumentu HTML na model DOM jest zdefiniowane przez standard HTML. Możesz zauważyć, że przesyłanie kodu HTML do przeglądarki
nigdy nie powoduje błędu. Może to być np. brak zamykającego tagu </p>
w prawidłowym kodzie HTML. Błędne znaczniki, takie jak Hi! <b>I'm <i>Chrome</b>!</i>
(tag b jest zamknięty przed tagiem i), są traktowane tak, jakby tekst Hi! <b>I'm <i>Chrome</i></b><i>!</i>
został napisany. Dzieje się tak, ponieważ specyfikacja HTML została zaprojektowana w taki sposób, aby zapewniać obsługę takich błędów. Jeśli ciekawi Cię, jak to się robi, przeczytaj sekcję „Wprowadzenie do obsługi błędów i dziwne przypadki w parserze” specyfikacji HTML.
Wczytuję zasób podrzędny
Witryna zwykle korzysta z zasobów zewnętrznych, takich jak obrazy, CSS i JavaScript. Pliki te muszą być wczytywane z sieci lub pamięci podręcznej. Wątek główny może żądać ich po kolei, gdy je znajdują, gdy są analizowane w celu utworzenia modelu DOM. Aby jednak przyspieszyć działanie, równolegle działa „wstępnie załaduj skaner”.
Jeśli w dokumencie HTML znajdują się elementy typu <img>
lub <link>
, skaner wyświetla tokeny wygenerowane przez parser HTML i wysyła żądania do wątku sieciowego w procesie przeglądarki.
JavaScript może blokować analizę
Gdy parser HTML znajdzie tag <script>
, wstrzymuje analizę dokumentu HTML oraz musi wczytać, przeanalizować i wykonać kod JavaScript. Dlaczego? Ponieważ JavaScript może zmieniać kształt dokumentu za pomocą elementów takich jak document.write()
, co zmienia całą strukturę DOM (omówienie modelu analizy w specyfikacji HTML zawiera ładny diagram). Dlatego parser HTML musi czekać na uruchomienie JavaScriptu, zanim będzie mógł wznowić analizę dokumentu HTML. Jeśli ciekawi Cię, jak wygląda wykonywanie kodu JavaScript, zespół V8 publikuje na ten temat wykłady i posty.
Podpowiedź do przeglądarki, jak chcesz wczytywać zasoby
Programiści stron internetowych mogą wysyłać wskazówki do przeglądarki na wiele sposobów, by poprawnie wczytywać zasoby.
Jeśli w Twoim kodzie JavaScript nie jest używany document.write()
, możesz dodać atrybut async
lub defer
do tagu <script>
. Następnie przeglądarka wczytuje i uruchamia kod JavaScript asynchronicznie, nie blokuje analizy. W razie potrzeby możesz też skorzystać z modułu JavaScript. <link rel="preload">
to sposób na poinformowanie przeglądarki, że zasób jest zdecydowanie potrzebny do aktualnej nawigacji i że chcesz go jak najszybciej pobrać. Więcej informacji na ten temat znajdziesz w artykule Określanie priorytetów zasobów – pomoc przeglądarce.
Obliczanie stylu
Model DOM nie wystarcza do poznania wyglądu strony, ponieważ możemy określać ich styl w CSS. Wątek główny analizuje kod CSS i określa styl obliczony dla każdego węzła DOM. To informacja o tym, jaki styl jest stosowany do poszczególnych elementów na podstawie selektorów arkusza CSS. Te informacje znajdziesz w sekcji computed
w Narzędziach deweloperskich.
Nawet jeśli nie podasz żadnego kodu CSS, każdy węzeł DOM ma styl obliczony. Tag <h1>
będzie większy niż tag <h2>
, a dla każdego elementu zostaną zdefiniowane marginesy. Wynika to z faktu, że przeglądarka
ma domyślny arkusz stylów. Jeśli chcesz się dowiedzieć, jaki jest domyślny kod CSS w Chrome, kod źródłowy znajdziesz tutaj.
Układ
Mechanizm renderowania zna już strukturę dokumentu i styl każdego węzła, ale to nie wystarczy, aby wyrenderować stronę. Wyobraź sobie, że próbujesz opisać znajomemu obraz przez telefon. „Duże czerwone koło i mały niebieski kwadrat” to za mało, aby znajomy mógł określić, jak dokładnie będzie wyglądać obraz.
Układ to proces znajdowania geometrii elementów. Wątek główny przedstawia DOM i obliczone style oraz tworzy drzewo układu z takimi informacjami jak współrzędne x y i rozmiary ramek ograniczających. Drzewo układu może mieć podobną strukturę do drzewa DOM, ale zawiera tylko informacje związane z tym, co jest widoczne na stronie. Jeśli zastosujesz parametr display: none
, ten element nie będzie częścią drzewa układu (chociaż drzewo układu będzie zawierać element z atrybutem visibility: hidden
). Podobnie jeśli zastosowana zostanie pseudoklasa z treścią taką jak p::before{content:"Hi!"}
, zostanie ona uwzględniona w drzewie układu, mimo że nie ma jej w DOM.
Ustalenie układu strony to nie lada wyzwanie. Nawet najprostszy układ strony, np. blok z góry na dół, musi uwzględniać wielkość czcionki i miejsce podziału wiersza, ponieważ wpływa to na rozmiar i kształt akapitu, a to z kolei wpływa na to, gdzie musi znaleźć się kolejny akapit.
CSS może powodować pływanie elementu na jednej stronie, maskowanie elementu przepełnienia i zmianę kierunku pisania. Wyobraź sobie, że ten etap układu to nie lada zadanie. W Chrome nad układem pracuje cały zespół inżynierów. Jeśli chcesz poznać szczegóły ich pracy, kilka rozmów z BlinkOn Conference jest nagrana i jest dość interesujący.
Barwiony
Zastosowanie DOM, stylu i układu to nadal za mało, aby wyrenderować stronę. Załóżmy, że próbujesz odtworzyć obraz. Znasz rozmiar, kształt i rozmieszczenie elementów, ale nadal musisz zdecydować, w jakiej kolejności je malujesz.
Na przykład dla niektórych elementów można ustawić z-index
. W takim przypadku malowanie w kolejności elementów napisanych w kodzie HTML może spowodować nieprawidłowe renderowanie.
Na tym etapie renderowania główny wątek przechodzi po drzewie układu, aby utworzyć rekordy renderowania. Rekord farby to notatka dotycząca procesu malowania, np. „pierwsze tło, potem tekst, a potem prostokąt”. Jeśli korzystasz z elementu <canvas>
za pomocą JavaScriptu, być może znasz już ten proces.
Aktualizacja potoku renderowania jest kosztowna
Najważniejszą rzeczą do zrozumienia w potoku renderowania jest to, że na każdym etapie do utworzenia nowych danych używany jest wynik poprzedniej operacji. Jeśli np. w drzewie układu coś się zmieni, trzeba ponownie wygenerować kolejność renderowania dla części dokumentu, w których występuje błąd.
Jeśli animujesz elementy, przeglądarka musi uruchamiać te operacje między każdą klatkami. Większość naszych wyświetlaczy odświeża ekran 60 razy na sekundę (60 kl./s). Animacja jest płynna dla ludzkich oczu, gdy przesuwasz elementy po ekranie w każdej klatce. Jeśli jednak w animacji brakuje klatek, na stronie będzie widać „problemy”.
Nawet jeśli operacje renderowania nadążają za odświeżaniem ekranu, obliczenia są wykonywane w wątku głównym, co oznacza, że mogą być blokowane, gdy aplikacja korzysta z JavaScriptu.
Możesz podzielić operację JavaScriptu na małe fragmenty i zaplanować uruchamianie w każdej klatce za pomocą requestAnimationFrame()
. Więcej informacji na ten temat znajdziesz w artykule Optymalizowanie wykonywania JavaScriptu. Możesz też uruchomić JavaScript w Web Workers, by uniknąć zablokowania wątku głównego.
Komponowanie
Jak narysować stronę?
Skoro przeglądarka zna już strukturę dokumentu, styl każdego elementu, geometrię strony i kolejność renderowania, jak rysuje stronę? Przekształcanie tych informacji w piksele na ekranie nazywamy rasteryzacją.
Być może naiwnym sposobem radzenia sobie z tym problemem jest rastrowanie fragmentów widocznych w widocznym obszarze. Jeśli użytkownik przewinie stronę, następnie przesunie ramkę rastrową i uzupełni brakujące części, stosując rastrowanie. Tak właśnie Chrome poradził z rasteryzacją w chwili premiery. Nowoczesna przeglądarka wykonuje jednak bardziej zaawansowany proces nazywany komponowaniem.
Czym jest komponowanie
Komponowanie to technika rozdzielania części strony na warstwy, rasteryzacji ich oddzielnie i tworzenia jako strony w oddzielnym wątku nazywanym wątkiem kompozytora. Jeśli przewija się, warstwy są już zrastrowane, wystarczy skomponować nową klatkę. Animację można uzyskać w ten sam sposób, przesuwając warstwy i skomponując nową klatkę.
W panelu Warstwy możesz zobaczyć, jak Twoja witryna jest podzielona na warstwy w Narzędziach deweloperskich.
Podział na warstwy
Aby dowiedzieć się, które elementy muszą znajdować się w poszczególnych warstwach, wątek główny przechodzi przez drzewo układu, aby utworzyć drzewo warstw (ta część nosi nazwę „Aktualizowanie drzewa warstw” w panelu wydajności Narzędzi deweloperskich). Jeśli dla niektórych części strony, które powinny być oddzielone warstwą (np. wysuwane menu boczne), nie są one widoczne, możesz poinformować przeglądarkę, używając atrybutu will-change
w CSS.
Nakładanie warstw na każdy element może być kuszące, ale komponowanie nadmiarowej liczby warstw może spowolnić działanie niż rasteryzowanie małych części strony w każdej klatce, dlatego ważne jest, aby mierzyć wydajność renderowania aplikacji. Więcej informacji znajdziesz w artykule na temat używania właściwości tylko do kompozytora i zarządzania liczbą warstw.
rastrowe i złożone z wątku głównego;
Po utworzeniu drzewa warstw i określeniu kolejności renderowania wątek główny zatwierdza te informacje w wątku kompozytora. Wątek kompozytora rasteryzuje każdą warstwę. Warstwa może być duża, jak cała długość strony, więc wątek kompozytora dzieli je na kafelki i wysyła każdy kafelek do wątków rastrowania. Wątki rastrowania rasteryzują każdy kafelek i są przechowywane w pamięci GPU.
Wątek kompozytora może nadawać priorytet różnym wątkom rastrowania, aby elementy w widocznym obszarze (lub w pobliżu) mogły być najpierw rastrowane. Warstwa ma też wiele kafelków w różnych rozdzielczościach, które pozwalają np. na powiększanie obrazu.
Po rastrowaniu kafelków wątek kompozytora zbiera informacje o kafelkach nazywane czworokątami rysunkowymi, aby utworzyć ramkę kompozytora.
Narysuj czworokąty | Zawiera informacje takie jak lokalizacja kafelka w pamięci i miejsce na stronie, w którym należy go narysować z uwzględnieniem jej kompilacji. |
Ramka kompozytora | Kolekcja czworokątów reprezentujących ramkę strony. |
Ramka kompozytora jest następnie przesyłana do procesu przeglądarki przez IPC. W tym momencie można dodać kolejną ramkę kompozytora z wątku UI na potrzeby zmiany interfejsu przeglądarki lub z innych procesów renderowania rozszerzeń. Te ramki kompozytora są wysyłane do GPU w celu wyświetlenia ich na ekranie. Jeśli pojawi się zdarzenie przewijania, wątek kompozytora tworzy kolejną ramkę kompozytora, która ma zostać wysłana do GPU.
Zaletą komponowania jest to, że odbywa się bez udziału wątku głównego. Wątek kompozytora nie musi czekać na obliczenie stylu ani wykonanie JavaScriptu. Dlatego komponowanie tylko animacji jest uznawane za najlepsze do zapewnienia płynności działania. Jeśli trzeba ponownie obliczyć układ lub wyrenderowanie, uwzględnia się wątek główny.
Podsumowanie
W tym poście przyglądaliśmy się potokowi renderowania – od analizowania do komponowania. Mam nadzieję, że teraz możesz przeczytać więcej o optymalizacji skuteczności witryny.
W następnym i ostatnim poście z tej serii omówimy bardziej szczegółowo wątek kompozytora i zobaczymy, co się stanie, gdy pojawią się dane wejściowe użytkownika takie jak mouse move
i click
.
Podobał Ci się post? Jeśli masz jakieś pytania lub sugestie dotyczące kolejnego posta, chętnie poznam Twoją opinię w sekcji komentarzy poniżej lub na Twitterze – @kosamari.