Trendy

Tajemnice renderowania stron internetowych

01.08.2019
Przeczytasz w 9 min.:

CSS jest językiem który na pierwszy rzut oka wydaje się bardzo prosty. Nie mamy do czynienia z pętlami czy warunkami logicznymi. Skupiamy się na prosto sprecyzowanym celu “ma być jak w projekcie”. Nie zastanawiając się czy można uprościć nasz kod lub go zoptymalizować. Podczas code review często nie zwracamy na niego należytej uwagi skupiając się tylko na logice biznesowej. Jednak wiele koncepcji CSS po ich poznaniu wydaje się prostszych w implementacji niż nasze twórcze pomysły. W moim odczuciu kod CSS jest jak scrum. “Łatwy do zrozumienia, trudny do opanowania”.

 

Zanim poznamy kilka koncepcji które pozwolą nam wynieść nasz css na wyższy poziom powinniśmy poznać sposób renderowania treści przez przeglądarki. Przeglądarki dzielą renderowanie stron internetowych na 3 kroki. Pierwszym krokiem jest obliczenie układu czyli wyznaczenie zależności miedzy innymi elementami na stronie. Podczas obliczania zależności bierzemy pod uwagę takie właściwości jak marginesy, szerokość czy zawartość w przypadku elementów display: inline. Jest to zazwyczaj bardzo rozbudowany proces ponieważ każdy element DOM może wpływać na inny element który jest w pobliżu. Gdy już znamy wyliczoną szerokość i wysokość wszystkich znaczników oraz ich pozycje możemy przejść do ich rysowania. Każdy znacznik na naszej stronie jest rysowany niezależnie. Podczas rysowania grafik przeglądarka trzyma się szerokości i wysokości obliczonej w poprzednim kroku. Ostatnim krokiem naszego procesu jest kompozycja która już z gotowych kawałków tworzy finalny wygląd strony internetowej biorąc pod uwagę np z-index.

 

Kolejne renderowanie naszej strony może się odbyć z pominięciem pierwszego kroku lub dwóch pierwszych w zależności od zmian które zachodzą podczas korzystania z naszej witryny. Gdy dokonamy zmiany w obrębie właściwości opacity przeglądarka pominie kosztowne obliczanie układu ponieważ przezroczystość nie wpływa na wielkość żadnego elementu a element przed zmianą przezroczystości zajmował już odpowiednie miejsce.

 

Przyspieszenie wczytywania

 

Zrozumienie procesu renderowania pomoże nam wykonywać płynne animacje CSS. Ale również możemy zyskać na szybkości wczytywania naszej strony internetowej. Jeśli zajmujecie się optymalizacją stron zapewne słyszeliście o tym aby w znacznikach <img> podawać szerokość i wysokość obrazka na stałe. Dzięki podaniu dokładnych wartości przeglądarka jest w stanie zarezerwować sobie odpowiednią ilość miejsca na obrazek który się wczyta w przyszłości. Gdy nie podajemy tych wartości przeglądarka jest zmuszona renderować naszą stronę od zera po wczytaniu obrazka. Dlaczego tak się dzieje? Gdy obrazek się wczyta przeglądarka otrzymuje informacje o jego wysokości i szerokości. Gdy te wartości ulegają zmianie przeglądarka jest zmuszona ponownie obliczyć układ czyli wracamy do pierwszego kroku renderowania. Podawanie szerokości i wysokości w znaczniku <img> obrazka jest ciekawą ideą jednak podczas stosowania tego rozwiązania tracimy możliwość tworzenia responsywnych obrazków. 

 

Jednak możemy opracować inne sposoby na rezerwowanie miejsca dla obrazów tak aby po wczytaniu obrazki posiadały responsywność. Oto dwa przykłady korzystające z pewnego haka w CSS. Ale spokojnie nie będziemy hakować NASA w tym języku! Wystarczy nam aby przeglądarka zrozumiała nasz zapis który na pierwszy rzut oka wygląda bardzo niezrozumiale. Aby zarezerwować prostokątne miejsce na stronie które będzie zachowywało proporcje musimy znać stosunek szerokości do wysokości. Załóżmy że nasze zdjęcie ma wymiary 660px szerokości na 264px wysokości. Dzięki tym danym możemy łatwo obliczyć (wysokość * 100 / szerokość) że wysokość posiada 40% szerokości. Gdy już mamy obliczone proporcje przechodzimy do pisania kodu:

 

<style>

    .img {

        width: 100%;

        max-width: 660px;

        display: inline-block;

    }

 

    .img__container {

        height: 0px;

        padding-bottom: 40%;

    }

</style>

 

<div class=”img”>

    <div class=”img_container”>

        <!– posiadam 264px wysokości i 660px szerokości –>

    </div>

</div>

 

Dzięki takiemu zapisowi znacznik który otrzyma style będzie skalował się jak prawdziwy obrazek. Magia polega na podaniu wartości padding w procentach. Jednostka % w właściwości padding zawsze odnosi się do szerokości. Dzięki temu jestesmy w stanie zarezerwować odpowiednią ilość miejsca i posiadać skalowanie proporcjonalne. Obecnie stworzyliśmy pusty znacznik bez żadnej treści, aby w środku takiego elementu pokazać obrazek możemy skorzystać z pozycji absolutnej lub umieścić obrazek jako tło tego znacznika. Przetestujmy więc która koncepcja sprawdza się najlepiej w praktyce! 

 

 

Testy mnie zdecydowanie zaskoczyły. Mierzyłem jak długo będą trwały procesy malowania i renderowania ośmiu obrazów o różnych wielkościach. Podczas testu ograniczyłem prędkości internetu w narzędziach deweloperskich do fast 3G. Prędkość internetu zmniejszyłem aby zmusić przeglądarkę do renderowania kawałków obrazków oraz przeliczania układu po wczytaniu tylko części. Najlepiej wypadły pomysły z zarezerwowaniem miejsca poprzez trik z paddingiem niezależnie czy obraz był pozycjonowany absolutnie czy wstawiony jako tło znacznika. Poniżej chciałem przedstawić wam średni czas każdego z rozwiązań.

 

Średni czas dla 10 testów: Znacznika img Znacznika img z szerokością i wysokością Zdjęcie jako background Zdjęcia pozycjonowane absolutnie
Malowania 23,7 ms  26,3 ms  15,2 ms 19,3 ms
Renderowania 11,0 ms  9,7 ms  13,3 ms 10,3 ms
Obu procesów 34,7 ms  36,0 ms  28,5 ms 29,7 ms

 

Średni czas całości najlepiej wypadł dla propozycji umieszczenia zdjęcia jako background dla diva. Rozwiązanie wygrało mimo tego, że posiadało najgorszy czas renderowania. Do renderowania  zaliczamy przeliczanie układu i kompozycje. Najbardziej zaskoczył mnie wynik rozwiązania zalecanego czyli dopisanie atrybutów width i height. Wydawać by się mogło, że to najlepsza droga ze względu na brak obliczeń podczas wyliczania układu. I racja zaoszczędziliśmy na obliczaniu układu o czym świadczy najniższy wynik renderowania jednak nie wiedzieć czemu proces malowania obrazu spowolnił. Wyniki wskazują, że oba rozwiązania oparte o procentową wartość w padding się sprawdzają dobrze. Różnice 1,2 ms możemy traktować jako błąd statystyczny i założyć, że procesy są bardzo zbliżone do siebie. Po przeprowadzonych testach mogę stanowczo powiedzieć, że warto zastosować ten trik na zdjęciach jeśli tylko możemy. Test przeprowadzałem tylko przy 8 zdjęciach i kilku znacznikach html. Mimo tak małego rozmiaru testu mogliśmy osiągnąć różnice w granicach 7,5 ms a im większa strona tym większa różnica. Dodatkowo pozbywamy się efektu “układania” strony w trakcie ładowania co daje wrażenie dużo szybszego wczytania strony.

 

Niesforne animacje

 

Ufff pierwszy temat za nami już wiemy jak tworzyć znaczniki <img> które nie spowalniają renderowania naszej strony! Następnym tematem który poruszymy są animacje na stronie. Mam nadzieje, że nikt z was już nie stosuje przestarzałego sposobu animowania wymyślonego przez jQuery w pierwszych wersjach i znacie właściwość transition w css. Jeśli tak to zapraszam do poznania właściwości will-change która też jest związana z animacjami. 

Wróćmy na chwile do tematu renderowania. Pamiętacie? Dzielimy je na 3 części (obliczanie układu, renderowanie elementów, kompozycja). Po operacji kompozycji większośc danych obliczonych podczas przeliczania układu przepada aby nie zaśmiecać pamięci RAM. Dzięki will-change jesteśmy w stanie wskazać w jakich elementach obliczone wcześniej wartości mogą się przydać na potem. Dzięki tej operacji przeglądarka nie usunie ich z pamięci i będzie w stanie szybciej zareagować na animacje które wymagają dodatkowych obliczeń takich jak przeliczanie układu. 

Jednak will-change nie jest lekiem na całe zło. Zabiera pamięć operacyjną dlatego też nie powinniśmy go stosować dla elementów których nie animujemy w danej chwili. Podczas optymalizacji należy też pamiętać o anty-wzorcu projektowym – “przedwczesna optymalizacja”. will-change może pomóc ale może też zaszkodzić więc dodawanie właściwości do wszystkich animacji nie będzie dobrym pomysłem. W pierwszej kolejności powinniśmy zwrócić uwagę jakie właściwości animujemy!

Ukrywanie i pojawianie elementów często robimy poprzez zmiany właściwości display. Gdy zmieniamy tę właściwość zmienia się układ naszej strony. Dlatego renderowane musi odbyć swoją drogę zaczynając od pierwszego kroku w którym obliczany jest układ elementów. Najlepszym sposobem na płynne animacje jest stosowanie opacity. Gdy element jest ukryty ale “zajmuje swoje miejsce” zostanie wyświetlony znacznie płynniej. Problemem w tym podejściu jest fakt, że gdy stosujemy opacity możemy dalej klikać w niewidoczne elementy. Rozwiązaniem tego problemu jest bardzo ciekawa właściwość CSS o nazwie pointer-events którą w naszym przypadku możemy ustawić na none podczas gdy element jest ukryty.

Selektory CSS

Zanim wyrenderujemy nasz pierwszy obraz strony w oparciu o CSS przeglądarka musi przetworzyć pliki HTML i CSS oraz powiązać ze  sobą DOM i CSSOM. Gdy to zrobi dopiero odbywa się renderowanie z użyciem CSSa. Za szybkość powiązania ze sobą tych odrębnych elementów odpowiadają w głównej mierze selektory które użyliśmy w plikach CSS. Warto poznawać sposób w który przeglądarki analizują nasze selektory CSS i nauczyć się wykorzystywać je lepiej. Wbrew temu co wydawało by się nam naturalne selektory CSS czytane są od prawej strony. Jest to związane z relacją wielu znaczników HTML do wielu selektorów CSS. Wydajniejszym sposobem w tym przypadku jest analizowanie wszystkich znaczników html po kolei i sprawdzanie dla nich wszystkich reguł. Gdy sprawdzamy selektor od końca zazwyczaj przeglądarce udaje się go o wiele szybciej odrzucić jako nie pasujący. Aby pisać wydajne selektory nasz ostatni element w selektorze powinien odfiltrować jak najwięcej znaczników HTMLa. Przykładem źle zoptymalizowanego seletora może być niewinnie wyglądający selektor często używany w pierwszym lekcjach CSSa.

ul.menu  li a span

Często w przykładach możemy zwrócić uwagę na wymienianiu każdego elementu osobno. Najgorszym przykładem tego połączenia jest użycie bardzo popularnego znacznika  na naszej stronie w tym wypadku span. Aby znaleźć wszystkie pasujące znaczniki przeglądarka najpierw wyszuka wszystkie znaczniki span a następnie odfiltruje te których rodzicami są znaczniki a. To może być wciąż dużo elementów. Dopiero w 3 etapie mamy znacznik li który sprecyzuje nam prawdopodobnie większość znaczników span które chcemy pobrać. Na samym końcu dla pewności sprawdzamy jeszcze czy całość jest w znaczniku ul z klasą menu. Jak zoptymalizować ten selektor? Najprostszym sposobem będzie dopisanie klasy do naszych znaczników span i właśnie ten przykład sobie porównamy.

ul.menu  li a .menu__span

Przetestujemy jeszcze uproszczony selektor czyli :

.menu__span

 

Jesteście ciekawi wyników ? Ja też ! Aby zbudować strukturę HTML do tego testu użyjemy emmeta. Chciałbym aby wygenerowana strona posiadała wiele elementów a i span oraz tylko jedno menu.

 

html>head+body>((ul.menu>li*6>a>span.menu_span>Lorem2)+(.container>((aside>ul*4>li*7>a>span>Lorem1)+(article>(p*20>span*3>Lorem2)+p*20>Lorem120)))+(.footer>ul*4>li*6>a>span>Lorem2))*50

 

Na potrzeby testu przygotowałem kod który wyglądem może przypominać stronę internetową.  Mamy sporo znaczników span, a wiele z nich jest dzieckiem znacznika a. Dzięki takiej strukturze wiele elementów których szukamy na końcu selektora (a span) będzie wymagało dodatkowej pracy z strony przeglądarki. Kod html w każdym teście był dokładnie taki sam. W testach mierzyłem szybkość eventu DOMContentLoaded który uruchamia się po powiązaniu HTMLa z CSSem. Dla każdego selektora przeprowadziłem 70 testów i stworzyłem wykres który nic nie mówi.

Było widać, że wyniki są bardzo zbliżone dlatego też wszystkie czasy posortowałem aby łatwiej było dostrzec który selektor wygrał nasz pojedynek. Testy przeprowadziłem również na przeglądarce firefox.

Wyniki są bardzo zbliżone więc oprócz wykresów chciałbym się z wami podzielić tabelką z średnimi wartościami dla każdego z selektorów.

 

Jak widzimy różnica jest bardzo mała jednak pokazuje to co chciałem wam przekazać. Ostatni element naszego selektora wpływa na wydajność całości. Jednak nasuwa się pytanie czy warto optymalizować selektory? Oczywiście, że nie! Nie powinniśmy w naszych aplikacjach optymalizować selektorów już istniejących skoro mają one tak mały wpływ przy tak ogromnym htmlu który stworzyliśmy na potrzeby tego testu. Spytacie więc co możemy zrobić z wykorzystaną wiedzą na temat selektorów? Unikać źle napisanych frameworków CSSowych oraz korzystać z metodologii CSSowych takich jak BEM! Metodologia BEM ułatwi zarządzanie kodem CSS oraz przyspieszy nasze selektory. Jeśli jeszcze nie znasz metodologii BEM powinieneś się nauczyć jej jak najszybciej! 

 

https://budzis.pl/Wpisy/Kursy/Optymalizacja-stron/Metodologia-BEM/

 

Selektory powinni zwrócić naszą uwagę w przypadku wyboru frameworków. W ramach podsumowania wykonałem jeszcze jeden test porównujący grida dwóch frameworków CSSowych. bootstrapa 4 oraz semantic UI. CSSy których użyłem do testów posiadały tylko potrzebny kod dla stworzenia siatki. Na potrzeby tego testu użyłem HTMLa który mógł współpracować z dwoma frameworkami na raz, a sam CSS był dodany w znaczniku <style> aby uniknąć wczytywania po sieci. 

 

html>head+body>.grid.container*32>.row.three.column.row*32>.col-lg-2.large-2.columns.column*32

 

W tym przypadku widzimy już większa różnice. 

 

Jak widzimy semantic UI jest daleko w tyle za bootstrapem. Głównie, że względu na tworzenie mało sprecyzowanych selektorów opartych o wartości atrybutów które całkowicie przeczą pisaniu czystego kodu oraz optymalizacji selektorów CSS. Bootstrap do zapisania kolumny używa selektora .col-3 natomiast semantic [class*=”four wide”].column. Semantic UI w moim odczuciu obrał złą ścieżkę z której nie da się zawrócić bez zmiany nazwy. Podejście semantyczne po prostu nie sprawdza się w plikach CSS. 

 

To już jest koniec

Wreszcie dobrnęliśmy do końca! Wiem, że nie było to łatwe z racji tego, że artykuł jest dość rozbudowany. Cieszę się, że mój artykuł zaciekawił Cię na tyle aby dotrwać do tego momentu. Podsumowując wszystkie nasze doświadczenia należy pamiętać o 3 rzeczach. 

Po pierwsze, zawsze rezerwujemy miejsce na obrazki na stronie. Zyskujemy trochę na szybkość renderowania oraz sprawimy dobre wrażenie szybko poukładanej strony. Dzięki zarezerwowaniu miejsca użytkownik będzie mógł bezpiecznie klikać w linki nie myśląc o tym, że nagle coś się przesunie i kliknie w zupełnie inny element.

Po drugie, powinniśmy unikać wykonywania przedwczesnych optymalizacji. Gdy nie ma takiej potrzeby nie powinniśmy stosować właściwości takich jak will-change.

 

Po trzecie, podczas pisania CSSa powinniśmy stosować wybraną przez siebie metodologię aby tworzyć wydajne selektory oraz reużywalny kod CSS. Stosując metodologię taką jak BEM unikniemy tworzenia nadmiarowego kodu 

Dzięki za uwagę! Jeśli masz jakieś pytania dotyczące artykuły chętnie na nie odpowiem w komentarzach.

Udostępnij
Czy ten artykuł był pomocny?
Tak0
Nie0