Logo David Burdelak
Webdevelopment

Jak zmniejszyć rozmiar DOM i przyspieszyć stronę

DOM (Document Object Model)

Przy audytach wydajności coraz częściej trafiam na strony, które ładują się przyzwoicie, a mimo to działają ociężale: przewijanie się zacina, menu otwiera się z opóźnieniem, a kliknięcie w filtr reaguje po chwili. Jedną z najczęstszych przyczyn jest zbyt rozbudowane drzewo DOM, czyli tysiące elementów HTML, z których większość nie jest użytkownikowi do niczego potrzebna. W tym wpisie pokazuję, jak mierzę rozmiar DOM, skąd biorą się zbędne elementy i jak je ograniczam w motywach, page builderach i aplikacjach JavaScript.

Dlaczego rozmiar DOM ma znaczenie

Każdy element na stronie to praca dla przeglądarki. Przy każdej zmianie, np. otwarciu menu czy dodaniu produktu do koszyka, musi ona ponownie dopasować style i przeliczyć układ części strony. Im więcej elementów i im głębiej są zagnieżdżone, tym dłużej to trwa i tym więcej pamięci zużywa strona. Najbardziej odczuwają to użytkownicy słabszych telefonów, a w danych widać to jako gorszy wynik Interaction to Next Paint, czyli opóźnioną reakcję na kliknięcia.

Rozmiar DOM wpływa też na skrypty. Każde wyszukanie elementów, każda pętla po węzłach i każda aktualizacja interfejsu w frameworku trwają dłużej, gdy drzewo jest ogromne. To element szerszego procesu, jakim jest działanie strony internetowej w przeglądarce: przeglądarka musi zbudować drzewo, zanim cokolwiek wyświetli, i przeliczać je przy każdej zmianie.

Jak mierzę rozmiar DOM

Najszybciej w konsoli narzędzi deweloperskich na otwartej stronie:

// liczba wszystkich elementów
document.querySelectorAll('*').length

// największa głębokość zagnieżdżenia
Math.max(...[...document.querySelectorAll('*')].map(el => {
  let depth = 0;
  while (el.parentElement) { el = el.parentElement; depth++; }
  return depth;
}))

Lighthouse i PageSpeed Insights pokazują ten sam problem w raporcie: łączną liczbę elementów, największą głębokość i element z największą liczbą bezpośrednich potomków. Ostrzeżenie pojawia się już przy kilkuset elementach, a wyraźny problem przy ponad 1400. Traktuję te liczby jako punkt orientacyjny, nie cel sam w sobie. Ważniejsze jest, czy strona reaguje płynnie przy realnym użyciu, co sprawdzam w zakładce Performance, klikając w menu, filtry i formularze.

Przy dużych serwisach sprawdzam rozmiar DOM dla każdego typu szablonu osobno: strony głównej, kategorii, produktu, wpisu. Zwykle problem dotyczy jednego lub dwóch szablonów, a nie całej strony.

Skąd biorą się tysiące zbędnych elementów

PrzyczynaJak to wygląda w kodzieCo robię
Page builderyKażdy blok to kilka zagnieżdżonych kontenerów: sekcja, kolumna, wrapper, widżetPrzejście na nowsze, płaskie kontenery oparte na Flexboxie, upraszczanie układów
Menu w dwóch wersjachPełne menu desktopowe i osobne menu mobilne, oba w kodzie każdej stronyJedno menu stylowane dla obu widoków
Mega menuSetki linków do wszystkich podkategorii w kodzie każdej podstronyOgraniczenie liczby linków, doładowanie zawartości przy pierwszym otwarciu
Ukryte treściWysuwane panele, okna modalne, zakładki i slidery obecne w kodzie, choć niewidoczneRenderowanie dopiero przy otwarciu lub ograniczenie liczby slajdów
Ikony SVG wklejane wielokrotnieTa sama ikona jako pełny kod SVG przy każdym produkcieSprite SVG i odwołania przez <use>
Długie listySetki produktów, komentarzy czy wierszy tabeli na jednej stroniePaginacja, „Pokaż więcej” lub wirtualizacja listy
Widżety zewnętrzneCzaty, opinie, kanały z mediów społecznościowych dokładające własne drzewa elementówŁadowanie po interakcji lub rezygnacja z części widżetów

Jak upraszczam strukturę w motywach i page builderach

Najwięcej daje zmiana podejścia do układu. Stare techniki wymagały dodatkowych kontenerów do wyrównania i odstępów. Dziś CSS Grid i Flexbox pozwalają uzyskać ten sam efekt na jednym elemencie nadrzędnym, bez zagnieżdżania kolejnych div-ów. Przy przebudowie szablonów w ten sposób zwykle wyraźnie zmniejszam liczbę elementów, bez żadnej zmiany w wyglądzie.

W page builderach, takich jak Elementor, przechodzę ze starych sekcji i kolumn na nowsze kontenery, które generują znacznie płytszą strukturę. Usuwam też widżety używane tylko do odstępów, puste kolumny i bloki ukryte na urządzeniach mobilnych, bo ukrycie w CSS nie usuwa ich z kodu. Często okazuje się, że największy problem nie leży w samym builderze, tylko w nadmiarze dodatków i efektów, czyli klasycznym overengineeringu.

Długie listy i treści poniżej pierwszego ekranu

Przy listach produktów nie wyświetlam setek pozycji naraz. Zamiast tego stosuję paginację albo przycisk „Pokaż więcej” z zachowaniem adresów kolejnych stron. W aplikacjach z bardzo długimi listami, np. tabelami w panelu, używam wirtualizacji, czyli renderowania tylko tych wierszy, które są aktualnie widoczne.

Dla rozbudowanych sekcji poniżej pierwszego ekranu pomocna jest właściwość CSS content-visibility: auto. Przeglądarka pomija wtedy układanie i rysowanie sekcji, dopóki użytkownik do niej nie przewinie. Elementy zostają w drzewie, więc treść jest dostępna dla wyszukiwarek i czytników ekranu, ale strona szybciej się wyświetla. Zawsze ustawiam przy tym szacowaną wysokość sekcji przez contain-intrinsic-size, żeby uniknąć przeskakiwania paska przewijania.

DOM w aplikacjach JavaScript

W aplikacjach opartych na frameworkach problemem jest nie tylko liczba elementów, ale też to, jak często się zmieniają. Kilka zasad, których pilnuję:

  • zmiany w drzewie wprowadzam hurtowo, np. budując fragment w pamięci i wstawiając go raz, zamiast dodawać elementy po jednym w pętli,
  • nie odczytuję wymiarów elementów zaraz po zmianie stylów w tej samej pętli, bo wymusza to wielokrotne przeliczanie układu,
  • komponenty niewidoczne, np. zamknięte okna modalne, odmontowuję zamiast tylko je ukrywać,
  • nie renderuję tej samej treści dwa razy dla różnych urządzeń, tylko dopasowuję ją stylami.

Po każdej zmianie porównuję liczbę elementów i nagranie z zakładki Performance przed poprawką i po niej. Zmniejszenie DOM o połowę rzadko zmienia wygląd strony, ale prawie zawsze zmienia to, jak szybko reaguje na użytkownika.