Polyfill zapytania w kontenerze

Gerald Monaco
Gerald Monaco

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 korzystania z niego, 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 pierwszą i najważniejszą rzeczą, którą musi zrobić polyfill, jest przetłumaczenie zapytania @container na 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.

Przed
@container (width > 300px) {
  /* content */
}
Po
@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.

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

Zwróć uwagę na użycie pseudoklasy :where(...). Dodanie dodatkowego selektora atrybutu zwiększa 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):

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

.card {
  color: red;
}
Po
@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, kod polyfill używa obu selektorów: oryginalnego selektora źródłowego, który służy do określania, czy element powinien otrzymać atrybut polyfill, oraz selektora transpilowanego, który 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:

Przed
@container (width > 300px) {
  #foo::before {
    /* ... */
  }
}
Po
@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, i jednego, który element może zastosować do siebie. Ten drugi atrybut jest używany w przypadku selektorów, które kierują 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 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. cqwcqh 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.

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 cqicqb, 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-typecontainer-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ę.

Przed
.card {
  container-name: card-container;
  container-type: inline-size;
}
Po
.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.

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 np. każdy element podrzędny .card będzie miał wartość --cq-XYZ-container-name--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 interfejsów API, które umożliwiają dostęp do nieprzetworzonego źródła zewnętrznej spersonalizowanej czcionki, może być konieczne jej 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 wygodnej obsługi, polyfill został zaprojektowany tak, aby nadawać priorytet opóźnieniu przy pierwszym działaniu (FID)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ł wczytywania 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 sprawdzimy, jakie wspaniałe rzeczy uda Ci się stworzyć.