Strona główna / Artykuły / Przełączanie wyświetlania poza ekranem za pomocą content-visibility i contain-intrinsic-size

Przełączanie wyświetlania poza ekranem za pomocą content-visibility i contain-intrinsic-size

Dowiedz się, w jaki sposób funkcja content-visibility: auto obniża koszty układu na długich stronach renderowanych przez serwer, dlaczego właściwość contain-intrinsic-size jest obowiązkowa oraz jak różni się od wirtualizacji.

2789 słów

Długie strony pełne powtarzających się elementów, takich jak listy produktów, kanały informacyjne, archiwa i duże tabele, często wydają się wolne, nawet jeśli zawarte w nich pliki JavaScript są niewielkie. Przyczyną jest zazwyczaj to, że przeglądarka oblicza style i układ dla każdego elementu na stronie, w tym setek kart, do których użytkownik jeszcze nie przewinął się i być może nigdy ich nie dotrze. Ten przewodnik pokazuje, w jaki sposób dwie właściwości CSS – content-visibility i contain-intrinsic-size – pozwalają przeglądarce odłożyć to obliczanie, jak ta zmiana wpływa na Core Web Vitals i indeksowanie oraz w jakich sytuacjach ta technika nie działa lub jest niewłaściwym narzędziem.

Typowy przypadek: długa lista, która działa wolno bez oczywistej przyczyny

Załóżmy stronę kategorii „Przeglądaj wszystkie produkty” w sklepie internetowym. Na serwerze generowana jest jedna długa strona zawierająca około 600 kart produktów, z których każda ma obrazek, tytuł, cenę i ocenę. Nie ma paginacji, ani funkcji przewijania w nieskończoność, ani wirtualizacji. Firma preferuje taki układ: jeden adres URL, możliwość przeglądania każdego produktu oraz dostęp do wszystkiego za pomocą funkcji wyszukiwania w stronie w przeglądarce.

Na laptopie średniej klasy strona potrzebuje prawie czterech sekund, zanim stanie się interaktywna. Najczęściej podejrzewa się JavaScript: zbyt duży plik, nieprawidłowo działająca funkcja useEffect, ponowne renderowanie komponentów w pętli. Dokładne sprawdzenie pliku nie wykazuje jednak niczego, co mogłoby tłumaczyć opóźnienie.

Panel wydajności w DevTools przedstawia inny obraz sytuacji. Większość czasu na głównej wątku jest poświęcona operacjom Layout, a dzieje się to zanim jakikolwiek skrypt zdąży mieć wpływ na działanie strony.

Dlaczego treść poza ekranem nadal kosztuje

Renderyzacja to nie jedna operacja. Przeglądarka najpierw ustala style dla każdego elementu, następnie przeprowadza rozkładanie, aby określić rozmiar i pozycję każdego elementu, a dopiero potem rysuje piksele. Rysowanie odbywa się głównie w obrębie tego, co znajduje się wewnątrz lub w pobliżu obszaru widoczności, ale ustalanie stylów i rozkładanie odbywają się w całym dokumencie, włączając treść znajdującą się daleko poniżej. Zanim przeglądarka zdecyduje, co narysować, kosztowne obliczenia geometryczne są już zakończone.

W przykładzie sklepu oznacza to, że wszystkie 600 kartek – z pudełkiem na obrazek, tytułem opakowania, ceną oraz wierszem oceny – przechodzą przez proces stylizacji i układu, zanim klient zobaczy pierwszą z nich. Klient widzi zapewne tylko osiem produktów. Pozostałe 592 są na razie bezużyteczne, ale przeglądarka nie ma sposobu, by wiedzieć, czy użytkownik kiedykolwiek przewinie stronę w dół, więc domyślnie traktuje je wszystkie jako treść, która musi być już gotowa. To właśnie ten marnotrawstwo należy wyeliminować. Jeśli chcesz ponownie zapoznać się z tym, w jaki sposób różne etapy tego procesu wpływają na koszty, sprawdź naszą analizę kosztów reflowu, repaintu i composite dla przeglądarki.

Rozwiązanie: dwie deklaracje dla elementu powtarzalnego

Zastosuj obie te właściwości do elementu, który się powtarza, w tym przypadku karty produktu. Pierwsza informuje przeglądarkę, że może pominąć renderowanie treści karty, gdy ta znajduje się daleko od obszaru widoczności. Druga dostarcza rozmiar zastępczy dla kart, których rzeczywisty rozmiar nie jest jeszcze znany.

.product-card {
  content-visibility: auto;
  contain-intrinsic-size: auto 340px;
}

Przy użyciu content-visibility: auto karta, która nie znajduje się blisko obszaru widoczności, pozostaje w DOM i w drzewie dostępności, a funkcja find-in-page nadal może znaleźć jej tekst. Zmienia się to tym, że przeglądarka nie poświęca zasobów na stylizację, układ i renderowanie jej treści, dopóki karta nie zbliży się do ekranu. W rzeczywistości ta właściwość stosuje mechanizmy kontroli układu, stylu i renderowania do elementu, co umożliwia przeglądarce traktowanie tego poddrzewa jako niezależnego i bezpieczne jego pominięcie.

Jak duże mogą być korzyści

W demonstracji opublikowanej na web.dev Google wziął długą stronę podzieloną na sekcje i skrócił czas jej renderowania z 232 ms do 30 ms, co stanowi około siedmiokrotny wzrost wydajności. Należy pamiętać, że jest to specjalnie stworzona strona demonstracyjna, a nie stronę produkcyjną. W przypadku rzeczywistej strony produktowej, takiej jak ta opisana powyżej, bardziej realistycznym wynikiem jest poprawa o około 2 razy, co nadal może stanowić różnicę między stroną, która wciąż się ładowa, a stroną, która wygląda gotowa już po pierwszym narysowaniu.

Ekskluzywne przykłady pokazują maksymalne możliwości. Podczas wykładu na Chrome Dev Summit ta funkcja została zastosowana do ogromnej specyfikacji HTML jednostronicowej zawierającej ponad 270 000 węzłów DOM, a czas układu treści spadł z około 50 sekund do około 400 milisekund. Niewiele stron wygląda w ten sposób, ale ilustruje to, ile wysiłku wkładane jest w treści, których nikt nie może zobaczyć.

Pomaga precyzyjne określenie tego, co się dzieje. CSS nie sprawia, że procesor będzie szybszy. Mówisz przeglądarce, że duża część pracy nie musi być wykonywana od razu. Gdy strona zawiera 600 kart, a obszar widoku pokazuje tylko osiem, renderowanie wszystkich kart od razu rzadko jest najlepszym wykorzystaniem głównej wątku.

Dlaczego właściwość contain-intrinsic-size nie jest opcjonalna

Gdy użyjesz samodzielnie content-visibility: auto, pasek przewijania zaczyna skakać podczas przewijania, co łatwo pomylić z niezwiązanym błędem.

Powód jest prosty. Karta, której układ został pominięty, nie ma określonej wysokości, więc przeglądarka ustala jej rozmiar tak, jakby była pusta. Przy setkach pominiętych kart całkowita wysokość dokumentu jest mocno błędna, a pasek przewijania odzwierciedla tę błędną wartość. Gdy karty pojawiają się w polu widzenia i uzyskują swoją rzeczywistą wielkość, dokument się powiększa, a suwak się przesuwa.

contain-intrinsic-size określa rozmiar, który ma być przyjęty podczas pomijania elementu. Wartość 340px w przykładzie to szacunek dla kart, które jeszcze nie zostały wyrenderowane. Dokładność nie jest konieczna; rozsądny przybliżenie zapobiega widocznym drganiom suwaka.

Czym dodaje się słowo kluczowe auto

Słowo kluczowe auto łatwo przeoczyć, mimo że ma ogromne znaczenie. Dzięki auto, po wyrenderowaniu karty przeglądarka zapisuje jej rzeczywisty rozmiar. Jeśli karta później opuści obszar widoku i zostanie ponownie pominięta, przeglądarka użyje zapisanego rozmiaru zamiast twojego szacunku. Bez auto każda karta za każdym razem, gdy wychodzi poza obszar widoku, powraca do ustalonego szacunku.

Karty na ekranie zawsze wyświetlają się przy swojej rzeczywistej wysokości, niezależnie od okoliczności. Różnica pojawia się w przypadku treści o zmiennej wysokości: długiego nazwy produktu, który rozciąga się na dodatkową linię, lub znaku zniżki, który dodaje nowy wiersz. Bez użycia auto karty wracają do szacowanej wysokości, gdy opuszczają obszar widoczny na ekranie i zmienia się pozycja przewijania. Zawsze używaj auto.

Obsługa w przeglądarkach i stopniowe ulepszanie

Obsługa tego funkcji nie jest już ograniczona wyłącznie do Chrome. Chrome obsługuje content-visibility od wersji 85 (2020), Firefox od wersji 125, a Safari od wersji 18. W chwili pisania tekstu ma on status „Baseline Newly Available”, osiągnięty 15 września 2025 roku, co oznacza, że funkcja działa we wszystkich trzech głównych silnikach przeglądarek; sprawdź aktualne dane dotyczące kompatybilności, jeśli obsługujesz starsze wersje.

Przeglądarki, które nie rozumieją tej właściwości, po prostu ją ignorują i renderują wszystko tak jak zawsze. Dzięki temu jest to ulepszenie progresywne, które nie ma żadnych negatywnych skutków dla starszych klientów.

Jak łączy się to z Core Web Vitals i SEO

Motywacją jest tutaj wydajność, ale strony kategorii to dokładnie ten typ adresów URL, który zespoły zajmujące się wyszukiwaniem obserwują uważnie. Są one publiczne, dotyczą cennych zapytań, a Google mierzy ich wydajność dla rzeczywistych użytkowników.

Rozkład i proces malowania wpływają na dwa z trzech wskaźników Core Web Vitals, które Google wykorzystuje jako sygnały do rankingu:

  • Interaction to Next Paint (INP) odnosi bezpośrednią korzyść. Pomijanie procesu renderowania kart poza ekranem uwalnia główny wątek, dzięki czemu dotknięcia i kliknięcia otrzymują odpowiedź szybciej.
  • Najdłuższy czas renderowania treści (LCP) odnosi korzyści w sposób bardziej pośredni. Na długich stronach mniej elementów układu przed pierwszym wyświetleniem treści zazwyczaj umożliwia szybsze pokazanie dużych, widocznych treści.
  • Wiele stron niezwiązanych z handlem ma taką strukturę – od dokumentacji i feedów informacyjnych po archiwa postów na blogach, długie tematy dyskusji oraz artykuły z wieloma sekcjami. Tam, gdzie wiele podobnych elementów jest ułożonych pionowo, a większość z nich znajduje się poza ekranem, a strona jest publiczna, ta zmiana wpływa na wskaźniki mierzone przez Google.

    Należy uważać przy obiecywaniu poprawy pozycji strony w wynikach wyszukiwania. Obserwacja jednej strony przez kilka tygodni niewiele dowodzi, a pozycja zależy od znacznie więcej niż tylko jednego wskaźnika. Można racjonalnie oczekiwać zmian w prawidłowym kierunku w danych z Field Data, na przykład w PageSpeed Insights, gdy zgromadzi się wystarczająca liczba próbek rzeczywistych użytkowników.

    Korzyści nie ograniczają się tylko do stron publicznych. Tablice administracyjne, panele sterowania oraz narzędzia wewnętrzne cierpią na ten sam koszt renderowania 600 wierszy. Po zalogowaniu nie ma żadnych dodatkowych korzyści pod względem wyszukiwania, ale poprawa doświadczenia użytkownika jest taka sama i wynika z tego samego CSS.

    Czy pomijanie renderowania ukrywa treść przed robotami?

    To pierwsze pytanie, jakie zada ostrożny specjalista od SEO, i jest ono uzasadnione biorąc pod uwagę liczbę schematów ładowania opóźnionego, które sprawiają, że treść staje się niewidoczna dla botów. Kluczowa różnica polega na tym, że content-visibility: auto to optymalizacja renderowania, a nie zmiana widoczności. Treść istnieje w HTML i w DOM od chwili załadowania strony; tylko jej układ i renderowanie są odkładane do momentu, gdy element znajdzie się w polu widzenia.

    Googlebot nie przewija się w taki sam sposób jak człowiek. Zamiast tego używa niezwykle wysokiego obszaru wyświetlania do renderowania, a następnie analizuje powstały DOM. Karty, które są pomijane, ale istnieją, stanowią część tego DOM-u, więc każdy tytuł produktu i cena są indeksowane tak samo jak wszelkie inne treści.

    W przeciwieństwie do tego starszy model polegał na wstawianiu treści tylko w momencie wystąpienia zdarzenia przewijania. Boty nie generują takich zdarzeń, więc takie treści rzeczywiście mogłyby nie znaleźć się w indeksie. content-visibility nie może powodować tego problemu, ponieważ nic nie jest dodawane później; wszystko istnieje od samego początku.

    Jedna uwaga nie dotyczy samej właściwości. Jeśli strona buduje swój content za pomocą JavaScriptu po stronie klienta, pojawia się odrębny problem z SEO. Googlebot faktycznie wykonuje JavaScript, ale renderowanie jest kierowane do kolejki, co sprawia, że jest wolniejsze i mniej niezawodne, a wiele innych narzędzi do przeglądania stron źle radzi sobie z JavaScriptem. W przypadku treści publicznych należy renderować HTML na serwerze i dodatkowo użyć atrybutu content-visibility w CSS. Taka kombinacja zapewnia treść dostępną do przeglądania oraz lepsze wyniki w analizach.

    Porównanie z nieskończonym przewijaniem, wirtualizacją i dostosowanymi obserwatorami

    Nieskończone przewijanie, API z paginacją oraz wirtualizacja wszystkie adresują ten sam problem powolnych, długich stron, więc warto je uczciwie porównać.

    Ładowanie treści w trakcie przewijania i API z paginacją

    Ładowanie dodatkowych elementów w miarę przewijania przez użytkownika rozwiązuje problem na poziomie danych. Jest to właściwy wybór, gdy zbiór danych jest naprawdę ogromny i nie powinien być wysyłany do przeglądarki w całości.

    Ma to swoje koszty. Potrzebne są zmiany w API, stan ładowania, słuchacze przewijania oraz śledzenie stanu tego, co zostało już pobraane. Zmienia się również doświadczenie użytkownika: funkcja wyszukiwania w treści nie może znaleźć elementów, które jeszcze się nie załadowały, a dotarcie na koniec listy staje się uciążliwe. Na publicznej stronie kategorii pojawia się dodatkowe obciążenie dla SEO, ponieważ produkty widoczne tylko po przewinięciu nie są dostępne dla botów; konieczne są paginowane adresy URL jako alternatywa oraz dodatkowy kod, aby zachować możliwość indeksowania tych produktów. W przypadku 600 kart już obecnych w HTML oznacza to konieczność przepisania kodu oraz dodatkowej pracy nad SEO, aby naprawić problem, który w rzeczywistości dotyczy renderowania.

    Biblioteki wirtualizacji

    Wirtualizacja, wykorzystująca biblioteki takie jak react-window lub TanStack Virtual, przechowuje cały zbiór danych w pamięci, usuwając jednocześnie węzły DOM dla elementów znajdujących się poza widocznym oknem. To rozwiązanie działa i przy bardzo dużych skali jest lepszym wyborem, jak zostanie to omówione poniżej.

    Cena zależy od zależności JavaScript, konieczności przepisania komponentu oraz trudnego w obsłudze radzenia sobie z wierszami o zmiennej wysokości. Ponieważ usunięte elementy w ogóle nie znajdują się w DOM, narzędzie find-in-page nie może ich dostrzec, czytniki ekranu mają z nimi problemy, a na stronach publicznych boty przeglądarek je pomijają.

    Podejście oparte na ręcznym użyciu IntersectionObserver

    Rysowanie elementów samodzielnie, gdy IntersectionObserver zgłasza je jako widoczne, oznacza ponowne tworzenie w JavaScript na głównej wątku tego, co już robi content-visibility: auto, oraz ponowne pojawienie się błędów związanych z utrzymywaniem pozycji przewijania, które przeglądarka rozwiązała już w sposób wbudowany. Dziś nie ma większego powodu, by to pisać.

    Wybór między nimi

    Prawdziwym atutem content-visibility nie jest to, że przebija te techniki, lecz fakt, że jest o wiele tańszy w użyciu. Jest to jedna właściwość CSS: bez JavaScripta, bez zmian w API, bez konieczności przepisywania kodu, a treść pozostaje w DOM-ie dla celów wyszukiwania, technologii wspomagających oraz funkcji znajdź w stronie.

    Kompromis musi być jasno określony. content-visibility oszczędza zasoby potrzebne do renderowania, a nie pamięć. Każdy węzeł DOM nadal istnieje. Przy 600 kartach różnica jest znikoma. Przy 50 000 lub 100 000 elementach rozmiar DOM-u staje się samodzielnym problemem, i wtedy virtualizacja okazuje swoją skomplikowanieść. Zanim wybierzesz narzędzie, określ, z którym z tych dwóch problemów borykasz się – koszt renderowania czy rozmiar DOM-u.

    Gdzie działa i gdzie potajemnie psuje funkcjonowanie

    To nie jest właściwość, którą należy stosować wszędzie. Najlepiej sprawdza się tam, gdzie ta sama struktura powtarza się wiele razy, na przykład:

    • karty w katalogu lub liście
  • posty w mediach społecznościowych lub feedach informacyjnych
  • wiersze dużej tabeli danych
  • odpowiedzi pod artykułem
  • sekcje długiej strony dokumentacji
  • wpisy na stronie z wynikami
  • Każdy element, który stanowi jeden z wielu podobnych bloków ułożonych pionowo, przy czym większość z nich jest poza ekranem podczas ładowania, nadaje się do przetestowania.

    Mierzenie pominiętego treści daje błędne wyniki

    To rozwiązanie koliduje z kodem, który wymaga dokładnych danych geometrycznych z poddrzewa przed jego wyświetleniem. Typowym przykładem jest odczytywanie wysokości wewnętrznego elementu wiersza, który nadal znajduje się poza ekranem.

    const height = row
      .querySelector('.details')
      .getBoundingClientRect()
      .height;
    

    Mierzenie wewnętrznej zawartości pominiętego elementu za pomocą getBoundingClientRect() przed jego wyświetleniem daje wartości zerowe lub błędne. Właściwa ramka elementu podaje rozmiar miejsca zastępczego z contain-intrinsic-size, który również może nie odpowiadać rzeczywistości. Taki kod jest powszechny w rzeczywistych interfejsach, na przykład gdy:

    • umieszcza się podpowiedź obok elementu wyzwalającego
    • oblicza się wartości początkowe i końcowe dla animacji
    • decyduje się, gdzie ma się otworzyć menu rozwijane
    • dostosowuje rozmiar wierszy w wirtualnej liście
    • zarządza logiką pozycjonowania elementów przylegających
    • wyrównuje jeden komponent do drugiego

    Jeśli dokładna geometria ma znaczenie przed tym, jak treść stanie się widoczna, należy dokładnie przetestować rozwiązanie przed dodaniem tej właściwości.

    Inne nieodpowiednie rozwiązania

    • Zakotwiczone nagłówki oraz układy, których obliczenia działają poprawnie tylko wtedy, gdy wszystkie elementy potomne mają aktualną geometrię w tym samym momencie.
    • Cokolwiek znajdującego się powyżej linii widoku. Ten treść musi zostać wyświetlona natychmiast bez względu na wszystko, więc ta właściwość nie przynosi żadnych korzyści i wymaga dodatkowej obsługi.
    • Elementy, których efekty wizualne wykraczają poza ich obszar. Ponieważ właściwość ta kontroluje zasięg malowania, treść przekraczająca granice, taką jak duże cienie lub okna powiadomień umieszczone wewnątrz karty, może zostać obcięta na jej brzegu.

    Dwa mniej znane szczegóły

    content-visibility: hidden omija renderowanie w podobny sposób jak display: none, ale przeglądarka przechowuje w pamięci stan renderowania elementu. Ponowne pokazanie go jest znacznie tańsze niż odsłonięcie elementu z display: none, ponieważ nie trzeba robić tego od zera. Dzięki temu jest przydatny w kartach, menu poza ekranem oraz wirtualnych przewijakach. W odróżnieniu od auto, treść ustawiona na hidden nie jest dostępna za pomocą funkcji „find-in-page” podczas bycia ukrytą.

    Rеагowanie na zmiany stanu pomijania

    Każdy raz, gdy element używający content-visibility: auto przechodzi między stanem pomijanym a renderowanym, przeglądarka wysyła zdarzenie contentvisibilityautostatechange. Słuchanie tego zdarzenia pozwala zawiesić kosztowne skrypty, takie jak rysowanie na canvas, dla treści, które przeglądarka i tak nie renderuje.

    Weryfikacja efektu na własnych stronach

    Szybki eksperyment pokazuje wyraźną różnicę. Stwórz stronę testową z około 1000 kartami oraz przyciskiem, który dodaje lub usuwa wartość content-visibility: auto. Otwórz DevTools, przejdź do panelu Performance, nagraj ponowne załadowanie strony z wyłączonym przyciskiem, a następnie ponownie z włączonym i porównaj fioletowe bloki Layout w obu nagraniach.

    Następnie zastosuj tę samą weryfikację do swojej najdłuższej strony produkcyjnej. Zrób nagranie jej ładowania. Gdy dominuje część Layout, a interaktywność pojawia się późno, dodanie wartości content-visibility: auto do elementów powtarzających się jest często najtańszym możliwym rozwiązaniem: jedna właściwość, bez konieczności przepisywania kodu ani migracji do innego frameworka.

    Główne wnioski

    • Brauzery obsługują styl i układ całego dokumentu; content-visibility: auto pozwala im odłożyć tę pracę na elementy znajdujące się daleko od obszaru widoku, bez usuwania niczego z DOM-u.
    • Zawsze łącz to z contain-intrinsic-size: auto <estimate> w tej samej modyfikacji, w przeciwnym razie suwak będzie skakał.
    • Taki sposób zapewnia, że treść pozostaje indeksowalna, wyszukiwalna i dostępna, co odróżnia go od rozwiązań typu load-on-scroll i wirtualizacja.
    • Oszczędza czas renderowania, a nie pamięć; gdy sam DOM stanie się zbyt duży, właściwym rozwiązaniem jest wirtualizacja.
    • Nie stosuj tego nad pierwszą częścią strony, na elementach, których geometrię mierzy się przed wyświetleniem, oraz na komponentach, które rysują poza swoją własną przestrzenią.

    Literatura pokrewna