Zapytania o kontenery to nowa funkcja CSS, która umożliwia tworzenie logiki stylizacji, która kieruje się na cechy elementu nadrzędnego (np. jego szerokość lub wysokość), aby nadać styl elementom podrzędnym. Niedawno opublikowaliśmy dużą aktualizację polyfilla, która zbiegła się z dodaniem strony z informacjami o obsługiwanych przeglądarkach.
Z tego artykułu dowiesz się, jak działa polyfill, jakie problemy rozwiązuje i jakie są sprawdzone metody jego stosowania, aby zapewnić użytkownikom wygodę.
Na zapleczu
Transpilacja
Gdy parsujący CSS w przeglądarce napotka nieznaną regułę at-rule, np. nową regułę @container
, odrzuci ją, tak jakby nigdy nie istniała. Dlatego też pierwszą i najważniejszą rzeczą, którą musi zrobić polyfill, jest transpilacja zapytania @container
w coś, czego nie można odrzucić.
Pierwszym krokiem w transpilacji jest przekształcenie reguły @container
najwyższego poziomu w zapytanie @media. Dzięki temu treści pozostają pogrupowane. Na przykład podczas korzystania z interfejsów API CSSOM i podczas wyświetlania źródła kodu CSS.
@container (width > 300px) { /* content */ }
@media all { /* content */ }
Przed zapytaniem o kontenery autor nie miał możliwości arbitralnego włączania i wyłączania grup reguł. Aby wypełnić to zachowanie, reguły w zapytaniu kontenera muszą też zostać przekształcone. Każdy element @container
ma swój własny, unikalny identyfikator (np. 123
), który służy do przekształcania każdego selektora tak, aby był stosowany tylko wtedy, gdy element ma atrybut cq-XYZ
zawierający ten identyfikator. Ten atrybut zostanie ustawiony przez polyfill w czasie wykonywania.
@container (width > 300px) { .card { /* ... */ } }
@media all { .card:where([cq-XYZ~="123"]) { /* ... */ } }
Zwróć uwagę na użycie pseudoklasy :where(...)
. Dodanie dodatkowego selektora atrybutu zwiększy szczególność selektora. Dzięki pseudoklasie można zastosować dodatkowy warunek, zachowując pierwotną specyficzność. Aby zrozumieć, dlaczego jest to tak ważne, rozważ ten przykład:
@container (width > 300px) {
.card {
color: blue;
}
}
.card {
color: red;
}
W tym przypadku element z klasą .card
powinien zawsze mieć wartość color: red
, ponieważ późniejsza reguła zawsze zastąpi poprzednią regułę z tym samym selektorem i tą samą specyfiką. Przetłumaczenie pierwszej reguły i uwzględnienie dodatkowego selektora atrybutu bez :where(...)
zwiększyłoby specyficzność i spowodowałoby błędne zastosowanie reguły color: blue
.
Jednak pseudoklasa :where(...)
jest dosyć nowa. W przypadku przeglądarek, które nie obsługują tej funkcji, polyfill zapewnia bezpieczne i łatwe obejście problemu: możesz celowo zwiększyć specyficzność reguł, ręcznie dodając do reguł @container
selektor :not(.container-query-polyfill)
:
@container (width > 300px) { .card { color: blue; } } .card { color: red; }
@container (width > 300px) { .card:not(.container-query-polyfill) { color: blue; } } .card { color: red; }
Daje to kilka korzyści:
- Selektor w źródłowym pliku CSS uległ zmianie, więc różnica w konkretności jest wyraźnie widoczna. Jest to też dokumentacja, która pozwala określić, co jest objęte rozwiązaniem, gdy nie będziesz już potrzebować rozwiązania obejściowego ani polyfill.
- Specyficzność reguł będzie zawsze taka sama, ponieważ polyfill jej nie zmienia.
Podczas transpilacji polyfill zastąpi tę wartość zastępczą selektorem atrybutu o tej samej specyficzności. Aby uniknąć niespodzianek, polyfill używa obu selektorów: oryginalny selektor źródła służy do określenia, czy element powinien otrzymać atrybut polyfill, a selektor transpilowany służy do stylizacji.
Elementy pseudo
Możesz się zastanawiać, jak pseudoelementy, które nie mogą mieć atrybutów, mogą być obsługiwane, jeśli polyfill ustawia w elementach atrybut cq-XYZ
, aby zawierał unikalny identyfikator kontenera 123
.
Elementy pseudo są zawsze powiązane z prawdziwym elementem w DOM, który nazywa się elementem źródłowym. Podczas transpilacji selektor warunkowy jest stosowany do tego rzeczywistego elementu:
@container (width > 300px) { #foo::before { /* ... */ } }
@media all { #foo:where([cq-XYZ~="123"])::before { /* ... */ } }
Zamiast przekształcać się w #foo::before:where([cq-XYZ~="123"])
(co byłoby nieprawidłowe), selektor warunkowy jest przenoszony na koniec elementu źródłowego, czyli #foo
.
To jednak nie wszystko. Kontener nie może modyfikować niczego, co nie jest zawarowane w jego wnętrzu (a kontener nie może znajdować się wewnątrz samego siebie), ale zastanów się, co by się stało, gdyby #foo
był elementem kontenera, którego dotyczy zapytanie. Atrybut #foo[cq-XYZ]
zostanie błędnie zmieniony, a wszystkie reguły #foo
zostaną błędnie zastosowane.
Aby to naprawić, polyfill używa 2 atrybutów: jednego, który może być zastosowany do elementu tylko przez element nadrzędny, oraz drugiego, który element może zastosować do siebie. Ten drugi atrybut jest używany w przypadku selektorów, które kierują na pseudoelementy.
@container (width > 300px) { #foo, #foo::before { /* ... */ } }
@media all { #foo:where([cq-XYZ-A~="123"]), #foo:where([cq-XYZ-B~="123"])::before { /* ... */ } }
Kontener nigdy nie zastosuje pierwszego atrybutu (cq-XYZ-A
) do siebie samego, więc pierwszy selektor będzie pasować tylko wtedy, gdy inny nadrzędny kontener spełni warunki kontenera i je zastosuje.
Jednostki względne kontenera
Zapytania o kontenery zawierają też kilka nowych jednostek, których możesz używać w CSS, np. cqw
i cqh
odpowiadających odpowiednio 1% szerokości i wysokości (odpowiednio) najbliższego odpowiedniego kontenera nadrzędnego. Aby to umożliwić, jednostka jest przekształcana w wyrażenie calc(...)
za pomocą właściwości niestandardowych w CSS. Rozszerzenie polyfill ustawi wartości tych właściwości za pomocą stylów wbudowanych w elemencie kontenera.
.card { width: 10cqw; height: 10cqh; }
.card { width: calc(10 * --cq-XYZ-cqw); height: calc(10 * --cq-XYZ-cqh); }
Istnieją też jednostki logiczne, takie jak cqi
i cqb
, odpowiednio dla rozmiaru wstawionego i blokowego. Są one nieco bardziej skomplikowane, ponieważ osie inline i block są określane przez writing-mode
elementu korzystającego z jednostki, a nie elementu, którego dotyczy zapytanie. Aby to umożliwić, polyfill stosuje styl w źródle do każdego elementu, którego writing-mode
różni się od elementu nadrzędnego.
/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);
/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);
Teraz, tak jak wcześniej, jednostki można przekształcić w odpowiednią właściwość niestandardową CSS.
Właściwości
Zapytania dotyczące kontenera wprowadzają też kilka nowych właściwości CSS, takich jak container-type
i container-name
. Ponieważ interfejsów API, takich jak getComputedStyle(...)
, nie można używać z nieznanymi lub nieprawidłowymi właściwościami, są one również przekształcane w właściwości niestandardowe usługi porównywania cen po przeanalizowaniu. Jeśli nie można przeanalizować właściwości (np. dlatego, że zawiera ona nieprawidłową lub nieznaną wartość), pozostawia się ją do obsługi przez przeglądarkę.
.card { container-name: card-container; container-type: inline-size; }
.card { --cq-XYZ-container-name: card-container; --cq-XYZ-container-type: inline-size; }
Te właściwości są przekształcane, gdy tylko zostaną wykryte, co pozwala na prawidłowe działanie polyfilla z innymi funkcjami CSS, takimi jak @supports
. Ta funkcja jest podstawą sprawdzonych metod korzystania z polyfill, które opisujemy poniżej.
@supports (container-type: inline-size) { /* ... */ }
@supports (--cq-XYZ-container-type: inline-size) { /* ... */ }
Domyślnie właściwości niestandardowe CSS są dziedziczone, co oznacza, że np. każdy element podrzędny .card
będzie miał wartość --cq-XYZ-container-name
i --cq-XYZ-container-type
. Właściwości natywne nie działają w taki sposób. Aby rozwiązać ten problem, polyfill wstawia następującą regułę przed wszystkimi stylami użytkownika, aby każdy element otrzymał początkowe wartości, chyba że zostanie celowo zastąpiony przez inną regułę.
* {
--cq-XYZ-container-name: none;
--cq-XYZ-container-type: normal;
}
Sprawdzone metody
Chociaż większość użytkowników będzie prawdopodobnie korzystała z przeglądarek z wbudowanym mechanizmem obsługi zapytań kontenera, warto zadbać o to, aby pozostali użytkownicy również mieli dobre wrażenia.
Podczas wczytywania początkowego musi się wydarzyć wiele rzeczy, zanim polyfill będzie mógł utworzyć układ strony:
- Rozszerzenie musi zostać załadowane i inicjowane.
- Arkusze stylów muszą zostać przeanalizowane i przetłumaczone. Ponieważ nie ma żadnych interfejsów API, które umożliwiają dostęp do nieprzetworzonego źródła zewnętrznej spersonalizowanej strony, może być konieczne asynchroniczne ponowne pobieranie, ale najlepiej tylko z pamięci podręcznej przeglądarki.
Jeśli polyfill nie rozwiąże tych problemów, może to spowodować pogorszenie wyników podstawowych wskaźników internetowych.
Aby ułatwić Ci zapewnienie użytkownikom przyjemnego doświadczenia, polyfill został zaprojektowany tak, aby nadawać priorytet opóźnieniu przy pierwszym działaniu (FID) i skumulowanemu przesunięciu układu (CLS), nawet kosztem największego wyrenderowania treści (LCP). Biblioteka polyfill nie gwarantuje, że zapytania dotyczące kontenera zostaną ocenione przed pierwszym wyrenderowaniem. Oznacza to, że aby zapewnić użytkownikom jak najlepsze wrażenia, musisz zadbać o to, aby wszystkie treści, na które wpływają zapytania kontenera, były ukryte, dopóki nie załaduje się polyfill i nie przetłumaczy on Twojego kodu CSS. Jednym ze sposobów na to jest użycie reguły @supports
:
@supports not (container-type: inline-size) {
#content {
visibility: hidden;
}
}
Zalecamy połączenie tego z czystą animacją wczytywania w CSS, która jest absolutnie umieszczona nad (ukrytymi) treściami, aby poinformować użytkownika, że coś się dzieje. Pełne demo tego podejścia znajdziesz tutaj.
Takie podejście jest zalecane z kilku powodów:
- Czysty moduł ładowania CSS minimalizuje obciążenie dla użytkowników korzystających z nowszych przeglądarek, zapewniając jednocześnie szybki feedback użytkownikom korzystającym ze starszych przeglądarek i wolniejszych sieci.
- Połączenie bezwzględnego pozycjonowania wczytywania z wartością
visibility: hidden
zapobiega przesunięciu układu. - Po załadowaniu polyfillu warunek
@supports
przestanie być spełniony, a Twoje treści zostaną odsłonięte. - W przypadku przeglądarek z wbudowanym wsparciem dla zapytań kontenera warunek nigdy nie zostanie spełniony, więc strona zostanie wyświetlona zgodnie z oczekiwaniami w ramach pierwszego wyrenderowania.
Podsumowanie
Jeśli chcesz używać zapytań kontenerowych w starszych przeglądarkach, wypróbuj polyfill. Jeśli napotkasz problemy, zgłoś je.
Nie możemy się doczekać, aż zobaczymy i spróbujemy wykorzystać w praktyce niesamowite możliwości tej usługi.