Zapytania kontenerów CSS: przypadki użycia i strategie migracji
Opublikowany: 2022-03-10Pisząc zapytania o media dla elementu interfejsu użytkownika, zawsze opisujemy, w jaki sposób ten element jest stylizowany w zależności od wymiarów ekranu. To podejście sprawdza się dobrze, gdy odpowiedź zapytania o media elementu docelowego powinna zależeć tylko od rozmiaru widocznego obszaru. Rzućmy okiem na następujący przykład responsywnego układu strony.
Jednak responsywne projektowanie stron internetowych (RWD) nie ogranicza się do układu strony — poszczególne składniki interfejsu użytkownika zwykle zawierają zapytania o media, które mogą zmieniać swój styl w zależności od wymiarów widocznego obszaru.
Być może zauważyłeś już problem z poprzednim stwierdzeniem — układ poszczególnych komponentów interfejsu użytkownika często nie zależy wyłącznie od wymiarów rzutni. Podczas gdy układ strony jest elementem ściśle powiązanym z wymiarami okienka ekranu i jednym z najwyższych elementów w HTML, komponenty interfejsu użytkownika mogą być używane w różnych kontekstach i kontenerach. Jeśli się nad tym zastanowić, rzutnia jest tylko kontenerem, a komponenty interfejsu użytkownika mogą być zagnieżdżane w innych kontenerach ze stylami, które wpływają na wymiary i układ komponentu.
Mimo że ten sam komponent karty produktu jest używany zarówno w górnej, jak i dolnej części, style komponentów zależą nie tylko od wymiarów okienka ekranu, ale także zależą od kontekstu i właściwości CSS kontenera (takich jak siatka w przykładzie), w których jest umieszczony.
Oczywiście możemy ustrukturyzować nasz CSS, więc obsługujemy odmiany stylów dla różnych kontekstów i kontenerów, aby ręcznie rozwiązać problem z układem. W najgorszym przypadku ta odmiana zostałaby dodana z nadpisaniem stylu, co doprowadziłoby do duplikacji kodu i problemów ze specyficznością.
.product-card { /* Default card style */ } .product-card--narrow { /* Style variation for narrow viewport and containers */ } @media screen and (min-width: 569px) { .product-card--wide { /* Style variation for wider viewport and containers */ } }
Jest to jednak raczej obejście ograniczeń zapytań o media niż właściwe rozwiązanie. Pisząc zapytania o media dla elementów interfejsu użytkownika, staramy się znaleźć „magiczną” wartość widocznego obszaru dla punktu przerwania, gdy element docelowy ma minimalne wymiary, w których układ nie ulega uszkodzeniu. Krótko mówiąc, łączymy „magiczną” wartość wymiaru rzutni z wartością wymiarów elementu . Ta wartość jest zwykle inna niż wymiar rzutni i jest podatna na błędy, gdy wewnętrzne wymiary kontenera lub układ ulegają zmianie.
Poniższy przykład pokazuje dokładnie ten problem — nawet jeśli zaimplementowano responsywny element karty produktu i wygląda dobrze w standardowym przypadku użycia, wygląda na uszkodzony, jeśli zostanie przeniesiony do innego kontenera z właściwościami CSS, które wpływają na wymiary elementu. Każdy dodatkowy przypadek użycia wymaga dodania dodatkowego kodu CSS, co może prowadzić do zduplikowania kodu, rozdęcia kodu i kodu trudnego do utrzymania.
Jest to jeden z problemów, które próbują rozwiązać zapytania kontenera. Zapytania kontenerowe rozszerzają istniejące funkcje zapytań o media o zapytania zależne od wymiarów elementu docelowego. Istnieją trzy główne korzyści z zastosowania tego podejścia:
- Style zapytań kontenera są stosowane w zależności od wymiarów samego elementu docelowego. Komponenty interfejsu użytkownika będą mogły dostosować się do dowolnego kontekstu lub kontenera.
- Programiści nie będą musieli szukać wartości wymiaru widocznego obszaru „magiczna liczba”, która łączy zapytanie o media w widocznym obszarze z docelowym wymiarem komponentu interfejsu użytkownika w określonym kontenerze lub w określonym kontekście.
- Nie ma potrzeby dodawania dodatkowych klas CSS ani zapytań o media dla różnych kontekstów i przypadków użycia.
„Idealna responsywna witryna internetowa to system elastycznych, modułowych komponentów, które można zmienić, aby służyły w wielu kontekstach”.
— „Zapytania o kontenery: jeszcze raz do wyłomu”, Mat Marquis
Zanim zagłębimy się w zapytania kontenerowe, musimy sprawdzić obsługę przeglądarki i zobaczyć, jak możemy włączyć funkcję eksperymentalną w naszej przeglądarce.
Obsługa przeglądarki
Zapytania kontenerowe to funkcja eksperymentalna, dostępna obecnie w wersji Chrome Canary w momencie pisania tego artykułu. Jeśli chcesz kontynuować i uruchomić przykłady CodePen w tym artykule, musisz włączyć zapytania kontenerowe w następującym adresie URL ustawień.
chrome://flags/#enable-container-queries
W przypadku korzystania z przeglądarki, która nie obsługuje zapytań kontenerowych, obok demonstracji CodePen zostanie dostarczony obraz przedstawiający zamierzony przykład działania.
Praca z zapytaniami kontenerowymi
Zapytania kontenerowe nie są tak proste, jak zwykłe zapytania o media. Będziemy musieli dodać dodatkowy wiersz kodu CSS do naszego elementu interfejsu użytkownika, aby zapytania kontenerowe działały, ale jest ku temu powód i omówimy to w dalszej części.
Własność przechowawcza
Właściwość CSS contain
została dodana do większości nowoczesnych przeglądarek i ma przyzwoite 75% wsparcie przeglądarek w momencie pisania tego artykułu. Właściwość contain
jest używana głównie do optymalizacji wydajności poprzez wskazywanie przeglądarce, które części (poddrzewa) strony mogą być traktowane jako niezależne i nie wpływają na zmiany innych elementów w drzewie. W ten sposób, jeśli zmiana nastąpi w pojedynczym elemencie, przeglądarka ponownie wyrenderuje tylko tę część (poddrzewo) zamiast całej strony. Za pomocą wartości właściwości include możemy określić, jakich typów contain
chcemy użyć — layout
, size
, czy paint
.
Istnieje wiele świetnych artykułów na temat właściwości contain
, które szczegółowo opisują dostępne opcje i przypadki użycia, więc skupię się tylko na właściwościach związanych z zapytaniami kontenerowymi.
Co wspólnego ma właściwość zawartości CSS, która jest używana do optymalizacji, z zapytaniami kontenera? Aby zapytania kontenerowe działały, przeglądarka musi wiedzieć, czy nastąpiła zmiana w układzie podrzędnym elementu, aby ponownie renderować tylko ten składnik. Przeglądarka będzie wiedziała, jak zastosować kod w zapytaniu kontenera do pasującego komponentu, gdy komponent zostanie wyrenderowany lub gdy zmieni się wymiar komponentu.
Użyjemy wartości layout
style
dla contain
, ale będziemy potrzebować również dodatkowej wartości, która sygnalizuje przeglądarce oś, w której nastąpi zmiana.
-
inline-size
Ograniczenie na osi inline. Oczekuje się, że ta wartość będzie miała znacznie więcej przypadków użycia, więc jest wdrażana jako pierwsza. -
block-size
Ograniczenie na osi bloku. Jest wciąż w fazie rozwoju i nie jest obecnie dostępny.
Jednym drobnym minusem właściwości contain
jest to, że nasz element układu musi być dzieckiem elementu contain
, co oznacza, że dodajemy dodatkowy poziom zagnieżdżenia.
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
.card { contain: layout inline-size style; } .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ }
Zwróć uwagę, że nie dodajemy tej wartości do bardziej odległej section
przypominającej rodzica i utrzymujemy kontener tak blisko elementu, którego dotyczy problem, jak to tylko możliwe.
„Wydajność to sztuka unikania pracy i sprawienia, by każda wykonywana praca była tak wydajna, jak to tylko możliwe. W wielu przypadkach chodzi o pracę z przeglądarką, a nie przeciwko niej”.
— „Wydajność renderowania”, Paul Lewis
Dlatego powinniśmy poprawnie zasygnalizować przeglądarce o zmianie. Zawijanie odległego elementu nadrzędnego właściwością contain
może przynieść efekt przeciwny do zamierzonego i negatywnie wpłynąć na wydajność strony. W najgorszym przypadku niewłaściwego użycia właściwości include układ może contain
nawet zepsuć, a przeglądarka nie wyrenderuje go poprawnie.
Zapytanie o kontener
Po contain
właściwości include do opakowania elementu karty możemy napisać zapytanie kontenera. contain
właściwość include do elementu z klasą card
, dzięki czemu możemy teraz uwzględnić w zapytaniu kontenera dowolny z jego elementów podrzędnych.
Podobnie jak w przypadku zwykłych zapytań o media, musimy zdefiniować zapytanie za pomocą właściwości min-width
lub max-width
i zagnieździć wszystkie selektory w bloku. Jednak do zdefiniowania zapytania kontenera będziemy używać słowa kluczowego @container
zamiast @media
.
@container (min-width: 568px) { .card__wrapper { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { min-width: auto; height: auto; } }
Zarówno card__wrapper
, jak i card__image
są dziećmi elementu card
, który contain
zdefiniowaną właściwość include. Gdy zastąpimy zwykłe zapytania o media zapytaniami kontenerowymi, usuniemy dodatkowe klasy CSS dla wąskich kontenerów i uruchomimy przykład CodePen w przeglądarce obsługującej zapytania kontenerowe, otrzymamy następujący wynik.
Pamiętaj, że zapytania kontenerowe nie są obecnie wyświetlane w narzędziach dla programistów Chrome , co utrudnia debugowanie zapytań kontenerowych. Oczekuje się, że w przyszłości do przeglądarki zostanie dodana odpowiednia obsługa debugowania.
Możesz zobaczyć, jak zapytania kontenerowe pozwalają nam tworzyć bardziej niezawodne i wielokrotnego użytku komponenty interfejsu użytkownika, które można dostosować do praktycznie każdego kontenera i układu. Jednak właściwa obsługa zapytań kontenerowych przez przeglądarkę jest nadal daleko w tej funkcji. Spróbujmy i zobaczmy, czy możemy zaimplementować zapytania kontenerowe za pomocą progresywnego ulepszania.
Progresywne ulepszanie i wypełniacze
Zobaczmy, czy możemy dodać rezerwę do wariacji klas CSS i zapytań o media. Możemy użyć zapytań o funkcje CSS z regułą @supports
, aby wykryć dostępne funkcje przeglądarki. Nie możemy jednak sprawdzić innych zapytań, więc musimy dodać sprawdzenie dla wartości contain: layout inline-size style
. Musimy założyć, że przeglądarki obsługujące właściwość inline-size
obsługują również zapytania kontenerowe.
/* Check if the inline-size value is supported */ @supports (contain: inline-size) { .card { contain: layout inline-size style; } } /* If the inline-size value is not supported, use media query fallback */ @supports not (contain: inline-size) { @media (min-width: 568px) { /* ... */ } } /* Browser ignores @container if it's not supported */ @container (min-width: 568px) { /* Container query styles */ }
Takie podejście może jednak prowadzić do powielania stylów, ponieważ te same style są stosowane zarówno w zapytaniu kontenerowym, jak i zapytaniu o media. Jeśli zdecydujesz się zaimplementować zapytania kontenerowe z progresywnym ulepszaniem, będziesz chciał użyć preprocesora CSS, takiego jak SASS, lub postprocesora, takiego jak PostCSS, aby uniknąć duplikowania bloków kodu i zamiast tego użyć mieszanek CSS lub innego podejścia.
Ponieważ ta specyfikacja zapytania kontenera jest wciąż w fazie eksperymentalnej, należy pamiętać, że specyfikacja lub implementacja może ulec zmianie w przyszłych wydaniach.
Alternatywnie możesz użyć wypełniaczy, aby zapewnić niezawodne rozwiązanie awaryjne. Chciałbym zwrócić uwagę na dwa wypełniacze JavaScript, które obecnie wydają się być aktywnie utrzymywane i zapewniają niezbędne funkcje zapytań kontenerowych:
-
cqfill
autorstwa Jonathana Neala
Wypełnienie JavaScript dla CSS i PostCSS -
react-container-query
autorstwa Chrisa Garcia
Niestandardowy hak i komponent dla React
Migracja z zapytań o media do zapytań dotyczących kontenerów
Jeśli zdecydujesz się zaimplementować zapytania kontenerowe w istniejącym projekcie, który korzysta z zapytań o media, musisz dokonać refaktoryzacji kodu HTML i CSS. Odkryłem, że jest to najszybszy i najprostszy sposób dodawania zapytań kontenerowych przy jednoczesnym zapewnieniu niezawodnego powrotu do zapytań o media. Rzućmy okiem na poprzedni przykład karty.
<section> <div class="card__wrapper card__wrapper--wide"> <!-- Wide card content --> </div> </section> /* ... */ <aside> <div class="card__wrapper"> <!-- Narrow card content --> </div> </aside>
.card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ } .card__image { /* ... */ } @media screen and (min-width: 568px) { .card__wrapper--wide { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { /* ... */ } }
Najpierw owiń główny element HTML, do którego zastosowano zapytanie o media, elementem, który contain
właściwość include.
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
@supports (contain: inline-size) { .card { contain: layout inline-size style; } }
Następnie umieść zapytanie o media w zapytanie o funkcję i dodaj zapytanie kontenera.
@supports not (contain: inline-size) { @media (min-width: 568px) { .card__wrapper--wide { /* ... */ } .card__image { /* ... */ } } } @container (min-width: 568px) { .card__wrapper { /* Same code as .card__wrapper--wide in media query */ } .card__image { /* Same code as .card__image in media query */ } }
Chociaż ta metoda powoduje pewne rozdęcie kodu i zduplikowany kod, używając SASS lub PostCSS można uniknąć duplikowania kodu programistycznego, dzięki czemu kod źródłowy CSS pozostaje możliwy do utrzymania.
Gdy zapytania kontenerowe otrzymają odpowiednią obsługę przeglądarki, możesz rozważyć usunięcie bloków kodu @supports not (contain: inline-size)
i kontynuować obsługę wyłącznie zapytań kontenerowych.
Stephanie Eckles opublikowała niedawno świetny artykuł na temat zapytań kontenerowych obejmujący różne strategie migracji. Polecam sprawdzić, aby uzyskać więcej informacji na ten temat.
Scenariusze przypadków użycia
Jak widzieliśmy w poprzednich przykładach, zapytania kontenerowe najlepiej sprawdzają się w przypadku komponentów wielokrotnego użytku o układzie zależnym od dostępnej przestrzeni kontenera, który może być używany w różnych kontekstach i dodawany do różnych kontenerów na stronie.
Inne przykłady obejmują (przykłady wymagają przeglądarki obsługującej zapytania kontenerowe):
- Komponenty modułowe, takie jak karty, elementy formularzy, banery itp.
- Adaptowalne układy
- Paginacja z różnymi funkcjonalnościami dla urządzeń mobilnych i stacjonarnych
- Zabawne eksperymenty ze zmianą rozmiaru CSS
Wniosek
Gdy specyfikacja zostanie zaimplementowana i szeroko obsługiwana w przeglądarkach, zapytania kontenerowe mogą stać się funkcją zmieniającą grę. Pozwoli to programistom na pisanie zapytań na poziomie komponentów, przesuwając zapytania bliżej powiązanych komponentów, zamiast używać odległych i ledwo powiązanych zapytań o media w obszarze widzenia. Spowoduje to powstanie bardziej niezawodnych, wielokrotnego użytku i konserwowalnych komponentów, które będą w stanie dostosować się do różnych przypadków użycia, układów i kontenerów.
W obecnej sytuacji zapytania kontenerowe są wciąż we wczesnej, eksperymentalnej fazie, a implementacja jest podatna na zmiany. Jeśli chcesz już dziś zacząć używać zapytań kontenerowych w swoich projektach, musisz dodać je za pomocą progresywnego ulepszania z wykrywaniem funkcji lub użyć wypełnienia JavaScript. Oba przypadki spowodują pewne obciążenie kodu, więc jeśli zdecydujesz się na użycie zapytań kontenerowych w tej wczesnej fazie, pamiętaj o zaplanowaniu refaktoryzacji kodu, gdy funkcja stanie się powszechnie obsługiwana.
Bibliografia
- „Zapytania o kontenery: przewodnik szybkiego startu” autorstwa Davida A. Herron
- „Przywitaj się z zapytaniami kontenerów CSS”, Ahmad Shadeed
- „Zatrzymywanie CSS w Chrome 52”, Paul Lewis
- „Pomaganie przeglądarkom w optymalizacji dzięki właściwości zawierającej CSS” — Rachel Andrew