Polyfill zapytania w kontenerze

Gerald Monaco
Gerald Monaco

Zapytania o kontenery to nowa funkcja CSS, która umożliwia pisanie logiki stylu kierowanej na cechy elementu nadrzędnego (np. jego szerokość lub wysokość) zgodnie ze stylem elementów podrzędnych. Niedawno opublikowaliśmy dużą aktualizację kodu polyfill, który zbiegł się z wprowadzeniem obsługi w przeglądarkach.

Z tego posta dowiesz się, jak działa kod polyfill, w jaki sposób radzi sobie z nim oraz jakie są sprawdzone metody jego używania, by zadbać o wygodę użytkowników.

Dla zaawansowanych

Transpilacja

Gdy parser CSS w przeglądarce natrafi na nieznaną regułę, taką jak zupełnie nowa reguła @container, odrzuci ją tak, jakby nigdy nie istniała. Dlatego pierwszą i najważniejszą rzeczą, jaką musi zrobić polyfill, jest przekształcenie zapytania @container w takie, które nie zostanie odrzucone.

Pierwszym krokiem transpilacji jest przekształcenie reguły @container najwyższego poziomu w zapytanie @media. Zapewnia to głównie to, że treści pozostają razem. na przykład gdy używasz interfejsów API CSSOM i wyświetlasz źródło CSS.

Przed
@container (width > 300px) {
  /* content */
}
Po
@media all {
  /* content */
}

Przed wysłaniem zapytań dotyczących kontenerów usługa porównywania cen nie umożliwiała autorowi dowolnych włączania lub wyłączania grup reguł. Aby zastosować kodowanie, musisz przekształcić również reguły zapytania dotyczącego kontenera. Każdy element @container ma własny unikalny identyfikator (np. 123), który jest używany do przekształcania każdego selektora w taki sposób, aby był stosowany tylko wtedy, gdy element ma atrybut cq-XYZ zawierający ten identyfikator. Ten atrybut zostanie ustawiony przez kod polyfill w czasie działania.

Przed
@container (width > 300px) {
  .card {
    /* ... */
  }
}
Po
@media all {
  .card:where([cq-XYZ~="123"]) {
    /* ... */
  }
}

Zwróć uwagę na pseudoklasę :where(...). Zwykle uwzględnienie dodatkowego selektora atrybutu zwiększy jego specyficzność. Dzięki pseudoklasie można zastosować dodatkowy warunek, zachowując pierwotną szczegółowość. Aby przekonać się, dlaczego jest to istotne, przyjrzyjmy się temu przykładowi:

@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}

Biorąc pod uwagę ten kod CSS, element z klasą .card powinien zawsze mieć parametr color: red, ponieważ późniejsza reguła zawsze zastępuje poprzednią regułę z tym samym selektorem i szczegółowością. Transpilacja pierwszej reguły i dodanie selektora dodatkowego atrybutu bez atrybutu :where(...) zwiększy dokładność i spowodowałoby błędne zastosowanie reguły color: blue.

Pseudoklasa :where(...) jest jednak dość nową. W przypadku przeglądarek, które go nie obsługują, bezpieczne i łatwe w obsłudze rozwiązanie jest polyfill. Możesz celowo zwiększyć szczegółowość reguł, ręcznie dodając do reguł @container przykładowy selektor :not(.container-query-polyfill):

Przed
@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}
Po
@container (width > 300px) {
  .card:not(.container-query-polyfill) {
    color: blue;
  }
}

.card {
  color: red;
}

Ma to wiele zalet:

  • Selektor w źródłowym kodzie CSS zmienił się, więc różnica w szczegółowości jest wyraźnie widoczna. Jest to również dokumentacja, z której dowiesz się, co wpływa na to, że nie musisz już korzystać z obejścia ani stosowania kodu polyfill.
  • Szczegółowość reguł będzie zawsze taka sama, ponieważ kod polyfill nie zmienia zakresu.

Podczas transpilacji kod polyfill zastąpi tę przykładową selektorem atrybutu o tej samej specyfice. Aby uniknąć niespodzianek, kod polyfill korzysta z obu selektorów – do określenia, czy element powinien otrzymać atrybut polyfill, używany jest pierwotny selektor źródła, natomiast do określania stylu używany jest transpilowany selektor.

Pseudoelementy

Możesz sobie zadać jedno pytanie: jeśli polyfill ustawia w elemencie atrybut cq-XYZ zawierający unikalny identyfikator kontenera 123, jak mogą być obsługiwane pseudoelementy, które nie mogą mieć ustawionych atrybutów?

Pseudoelementy są zawsze powiązane z rzeczywistym elementem w DOM, nazywanym elementem źródłowym. Podczas transpilacji selektor warunkowy jest stosowany do tego rzeczywistego elementu:

Przed
@container (width > 300px) {
  #foo::before {
    /* ... */
  }
}
Po
@media all {
  #foo:where([cq-XYZ~="123"])::before {
    /* ... */
  }
}

Zamiast przekształcenia do postaci #foo::before:where([cq-XYZ~="123"]) (co jest nieprawidłowe), selektor warunkowy jest przenoszony na koniec elementu źródłowego (#foo).

To jednak nie wszystko. Kontener nie może modyfikować niczego, co nie jest w nim (a kontener nie może się znajdować w samym środku), ale zastanów się, co dokładnie się stało, gdyby parametr #foo byłby elementem kontenera, którego dotyczy zapytanie. Atrybut #foo[cq-XYZ] zostałby zmieniony przez pomyłkę, a wszystkie reguły #foo zostałyby zastosowane przez pomyłkę.

Aby to poprawić, polyfill korzysta z 2 atrybutów: jednego, który może być stosowany do elementu tylko przez element nadrzędny, i drugiego, który element może mieć zastosowanie do niego samego. Ten ostatni atrybut jest używany w przypadku selektorów kierowanych na pseudoelementy.

Przed
@container (width > 300px) {
  #foo,
  #foo::before {
    /* ... */
  }
}
Po
@media all {
  #foo:where([cq-XYZ-A~="123"]),
  #foo:where([cq-XYZ-B~="123"])::before {
    /* ... */
  }
}

Kontener nigdy nie będzie stosować pierwszego atrybutu (cq-XYZ-A) do samego siebie, więc pierwszy selektor będzie dopasowywany tylko wtedy, gdy inny kontener nadrzędny spełni warunki kontenera i go zastosował.

Jednostki względne dla kontenera

Zapytania dotyczące kontenerów zawierają też kilka nowych jednostek, których możesz używać w swoich arkuszach CSS, np. cqw i cqh dla 1% szerokości i wysokości (odpowiednio) najbliższego, odpowiedniego kontenera nadrzędnego. Aby je obsługiwać, jednostka jest przekształcana w wyrażenie calc(...) za pomocą niestandardowych właściwości CSS. Kod polyfill ustawi wartości tych właściwości za pomocą stylów wbudowanych w elemencie kontenera.

Przed
.card {
  width: 10cqw;
  height: 10cqh;
}
Po
.card {
  width: calc(10 * --cq-XYZ-cqw);
  height: calc(10 * --cq-XYZ-cqh);
}

Istnieją też jednostki logiczne, takie jak cqi i cqb, które określają rozmiar elementu wbudowanego i rozmiar bloku (odpowiednio). Jest to trochę bardziej skomplikowane, ponieważ osie wbudowane i bloki są określane przez writing-mode elementu za pomocą jednostki, a nie przez element, którego dotyczy zapytanie. Aby to umożliwić, kod polyfill stosuje styl wbudowany 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 można tak jak wcześniej przekształcić jednostki w odpowiednią właściwość niestandardową CSS.

Właściwości

Zapytania dotyczące kontenerów dodają też kilka nowych właściwości CSS, np. container-type i container-name. Interfejsów API takich jak getComputedStyle(...) nie można używać z nieznanymi lub nieprawidłowymi właściwościami, dlatego są one przekształcane w właściwości niestandardowe CSS po przeanalizowaniu. Jeśli nie da się przeanalizować właściwości (np. dlatego, że zawiera nieprawidłową lub nieznaną wartość), pozostanie ona bez zmian przez przeglądarkę.

Przed
.card {
  container-name: card-container;
  container-type: inline-size;
}
Po
.card {
  --cq-XYZ-container-name: card-container;
  --cq-XYZ-container-type: inline-size;
}

Właściwości te są przekształcane po każdym wykryciu, dzięki czemu polyfill dobrze współgra z innymi funkcjami CSS, takimi jak @supports. Ta funkcja jest podstawą sprawdzonych metod korzystania z kodu polyfill, które opisaliśmy poniżej.

Przed
@supports (container-type: inline-size) {
  /* ... */
}
Po
@supports (--cq-XYZ-container-type: inline-size) {
  /* ... */
}

Domyślnie właściwości niestandardowe CSS są dziedziczone, co oznacza, że na przykład każdy element podrzędny elementu .card przyjmuje wartość --cq-XYZ-container-name i --cq-XYZ-container-type. Właściwości natywne nie działają w ten sposób. Aby rozwiązać ten problem, kod polyfill wstawi poniższą regułę przed stylami użytkownika, tak by każdy element otrzymał wartości początkowe, chyba że celowo zastąpi je inna reguła.

* {
  --cq-XYZ-container-name: none;
  --cq-XYZ-container-type: normal;
}

Sprawdzone metody

Spodziewa się, że większość użytkowników prędzej niż później będzie korzystać z przeglądarek z wbudowaną obsługą zapytań kontenerów, ważne jest jednak, aby zadbać o wygodę pozostałych użytkowników.

Zanim kod polyfill zacznie układać stronę podczas początkowego wczytywania, musi wystąpić wiele czynników:

  • Musisz załadować i zainicjować kod polyfill.
  • Arkusze stylów muszą zostać przeanalizowane i transpilowane. Ponieważ nie ma żadnych interfejsów API umożliwiających dostęp do surowego źródła zewnętrznego arkusza stylów, konieczne może być jego ponowne ładowanie asynchronicznie, najlepiej tylko z pamięci podręcznej przeglądarki.

Jeśli nie rozwiążesz tych problemów przez kod polyfill, może to spowodować pogorszenie wartości podstawowych wskaźników internetowych.

Aby ułatwić Ci dbanie o wygodę użytkowników, kod polyfill został zaprojektowany z myślą o opóźnieniu przy pierwszym działaniu (FID) i skumulowanym przesunięciem układu (CLS), potencjalnie kosztem największego wyrenderowania treści (LCP). Technologia polyfill nie gwarantuje, że zapytania dotyczące kontenerów zostaną ocenione przed pierwszym wyrenderowaniem. Oznacza to, że aby zadbać o wygodę użytkowników, musisz zadbać o to, aby wszelkie treści, na które rozmiar lub położenie miałyby wpływ na użycie zapytań kontenera, były ukryte do czasu załadowania i transpilacji kodu CSS przez kod polyfill. Można to osiągnąć na przykład za pomocą reguły @supports:

@supports not (container-type: inline-size) {
  #content {
    visibility: hidden;
  }
}

Zaleca się połączenie tego z animacją wczytywania wyłącznie z CSS, całkowicie umieszczoną nad (ukrytą) treścią, aby poinformować użytkownika o tym, że coś się dzieje. Pełną prezentację tej metody znajdziesz tutaj.

Jest to zalecane z kilku powodów:

  • Ładowanie w postaci czystego kodu CSS minimalizuje obciążenie związane z użytkownikami nowszych przeglądarek, zapewniając przy tym mniej informacji dla użytkowników starszych i wolniejszych sieci.
  • Łącząc bezwzględne pozycjonowanie modułu ładowania z elementami visibility: hidden, unikasz przesunięcia układu.
  • Po wczytaniu kodu polyfill ten warunek @supports przestanie spełniać warunki, a treść zostanie ujawniona.
  • W przeglądarkach z wbudowaną obsługą zapytań dotyczących kontenerów warunek nie zostanie spełniony, więc strona będzie się wyświetlać przy pierwszym wyrenderowaniu zgodnie z oczekiwaniami.

Podsumowanie

Jeśli chcesz korzystać z zapytań dotyczących kontenerów w starszych przeglądarkach, wypróbuj polyfill. Jeśli napotkasz jakieś problemy, zgłoś problem.

Nie możemy się doczekać, aby zobaczyć i przekonać się, jakie niesamowite rzeczy możesz dzięki nim stworzyć.

Podziękowania

Baner powitalny od Dana Cristiana Pădure Reklamy w aplikacji Unsplash.