Запросы CSS-контейнеров: варианты использования и стратегии миграции

Опубликовано: 2022-03-10
Краткое резюме ↬ Запросы контейнеров CSS приближают медиазапросы к самим целевым элементам и позволяют им адаптироваться практически к любому заданному контейнеру или макету. В этой статье мы рассмотрим основы контейнерных запросов CSS и способы их использования сегодня с прогрессивным улучшением или полифиллами.

Когда мы пишем медиа-запросы для элемента пользовательского интерфейса, мы всегда описываем, как оформляется этот элемент в зависимости от размеров экрана. Этот подход хорошо работает, когда скорость отклика медиазапроса целевого элемента должна зависеть только от размера области просмотра. Давайте взглянем на следующий пример адаптивного макета страницы.

Пример адаптивного макета страницы. Левое изображение показывает макет рабочего стола с шириной области просмотра 1280 пикселей, а правое изображение показывает макет для мобильных устройств с шириной области просмотра 568 пикселей.
Пример адаптивного макета страницы. Левое изображение показывает макет рабочего стола с шириной области просмотра 1280 пикселей, а правое изображение показывает макет для мобильных устройств с шириной области просмотра 568 пикселей. (Большой превью)

Однако адаптивный веб-дизайн (RWD) не ограничивается макетом страницы — отдельные компоненты пользовательского интерфейса обычно имеют медиа-запросы, которые могут менять свой стиль в зависимости от размеров области просмотра.

Пример компонента адаптивной карточки товара. На левом изображении показан компонент с шириной области просмотра 720 пикселей, а на правом изображении показан макет компонента с шириной области просмотра 568 пикселей.
Пример компонента адаптивной карточки товара. На левом изображении показан компонент с шириной области просмотра 720 пикселей, а на правом изображении показан макет компонента с шириной области просмотра 568 пикселей. (Большой превью)

Возможно, вы уже заметили проблему с предыдущим утверждением — макет отдельного компонента пользовательского интерфейса часто не зависит исключительно от размеров области просмотра. В то время как макет страницы — это элемент, тесно связанный с размерами области просмотра и являющийся одним из самых важных элементов в HTML, компоненты пользовательского интерфейса могут использоваться в различных контекстах и ​​контейнерах. Если подумать, область просмотра — это просто контейнер, а компоненты пользовательского интерфейса могут быть вложены в другие контейнеры со стилями, влияющими на размеры и макет компонента.

Пример макета страницы с одним и тем же компонентом пользовательского интерфейса карточки продукта в сетке из трех столбцов в верхней части и в списке в нижней части.
Пример макета страницы с одним и тем же компонентом пользовательского интерфейса карточки продукта в сетке из трех столбцов в верхней части и в списке в нижней части. (Большой превью)

Несмотря на то, что один и тот же компонент карточки продукта используется как в верхней, так и в нижней части, стили компонента зависят не только от размеров окна просмотра, но также от контекста и свойств CSS контейнера (например, сетки в примере), где он размещен.

Конечно, мы можем структурировать наш CSS, чтобы мы поддерживали вариации стиля для разных контекстов и контейнеров, чтобы вручную решить проблему макета. В худшем случае этот вариант будет добавлен с переопределением стиля, что приведет к дублированию кода и проблемам специфичности.

 .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 */ } }

Однако это скорее обходной путь для ограничений медиа-запросов, чем правильное решение. При написании медиа-запросов для элементов пользовательского интерфейса мы пытаемся найти «волшебное» значение области просмотра для точки останова, когда целевой элемент имеет минимальные размеры, при которых макет не ломается. Короче говоря, мы связываем «волшебное» значение размера области просмотра со значением размеров элемента . Это значение обычно отличается от размера области просмотра и подвержено ошибкам при изменении внутренних размеров контейнера или макета.

Пример того, как медиа-запрос не может быть надежно связан с размерами элемента. Различные свойства CSS могут влиять на размеры элементов внутри контейнера. В этом примере заполнение контейнера различается между двумя изображениями.
Пример того, как медиа-запрос не может быть надежно связан с размерами элемента. Различные свойства CSS могут влиять на размеры элементов внутри контейнера. В этом примере заполнение контейнера различается между двумя изображениями. (Большой превью)

Следующий пример демонстрирует именно эту проблему — несмотря на то, что адаптивный элемент карточки продукта был реализован и выглядит хорошо в стандартном варианте использования, он выглядит сломанным, если он перемещен в другой контейнер со свойствами CSS, влияющими на размеры элемента. Каждый дополнительный вариант использования требует добавления дополнительного кода CSS , что может привести к дублированию кода, раздуванию кода и коду, который трудно поддерживать.

См. Pen [Карты продуктов: различные контейнеры] (https://codepen.io/smashingmag/pen/eYvWVxz) Адриана Беса.

См. Карточки продуктов Pen: Различные контейнеры Адриана Беса.

Это одна из проблем, которые пытаются исправить запросы контейнера. Контейнерные запросы расширяют существующие функциональные возможности медиа-запросов за счет запросов, зависящих от размеров целевого элемента. Есть три основных преимущества использования этого подхода:

  • Стили запроса контейнера применяются в зависимости от размеров самого целевого элемента. Компоненты пользовательского интерфейса смогут адаптироваться к любому заданному контексту или контейнеру.
  • Разработчикам не нужно будет искать значение измерения области просмотра «магическое число», которое связывает медиа-запрос области просмотра с целевым измерением компонента пользовательского интерфейса в конкретном контейнере или конкретном контексте.
  • Нет необходимости добавлять дополнительные классы CSS или медиа-запросы для разных контекстов и вариантов использования.
«Идеальный адаптивный веб-сайт — это система гибких модульных компонентов, которые можно переназначить для использования в различных контекстах».

— «Контейнерные запросы: еще раз к взлому», Мэт Маркиз.

Прежде чем мы углубимся в контейнерные запросы, нам нужно проверить поддержку браузера и посмотреть, как мы можем включить экспериментальную функцию в нашем браузере.

Поддержка браузера

Контейнерные запросы — это экспериментальная функция, доступная в настоящее время в версии Chrome Canary на момент написания этой статьи. Если вы хотите следовать и запускать примеры CodePen в этой статье, вам нужно включить контейнерные запросы в следующем URL-адресе настроек.

 chrome://flags/#enable-container-queries
Запросы контейнера
(Большой превью)

Если вы используете браузер, который не поддерживает контейнерные запросы, изображение, демонстрирующее предполагаемый рабочий пример, будет предоставлено вместе с демонстрацией CodePen.

Еще после прыжка! Продолжить чтение ниже ↓

Работа с контейнерными запросами

Контейнерные запросы не так просты, как обычные медиа-запросы. Нам придется добавить дополнительную строку кода CSS в наш элемент пользовательского интерфейса, чтобы заставить контейнерные запросы работать, но для этого есть причина, и мы рассмотрим ее далее.

Защитное свойство

Свойство CSS contain было добавлено в большинство современных браузеров и на момент написания этой статьи поддерживается 75% браузеров. Свойство contain в основном используется для оптимизации производительности, подсказывая браузеру, какие части (поддеревья) страницы можно рассматривать как независимые и не повлияют на изменения в других элементах дерева. Таким образом, если изменение произойдет в одном элементе, браузер повторно отобразит только эту часть (поддерево), а не всю страницу. Со значениями свойства contains мы можем указать, какие типы contain мы хотим использовать — layout , size или paint .

Есть contain замечательных статей о свойстве contains, в которых более подробно описываются доступные параметры и варианты использования, поэтому я сосредоточусь только на свойствах, связанных с запросами к контейнеру.

Какое отношение свойство содержимого CSS, используемое для оптимизации, имеет к запросам контейнера? Чтобы контейнерные запросы работали, браузеру необходимо знать, происходит ли изменение в макете дочерних элементов, что он должен повторно отображать только этот компонент. Браузер будет знать, что нужно применить код в запросе контейнера к соответствующему компоненту, когда компонент визуализируется или изменяется размер компонента.

Мы будем использовать значения layout​ style​ для contain​ , но нам также потребуется дополнительное значение, которое сигнализирует браузеру об оси, в которой произойдет изменение.

  • inline-size
    Сдерживание на встроенной оси. Ожидается, что это значение будет иметь значительно больше вариантов использования, поэтому оно реализуется в первую очередь.
  • block-size
    Сдерживание на оси блока. Он все еще находится в разработке и в настоящее время недоступен.

Один незначительный недостаток свойства contains contain в том, что наш элемент макета должен быть дочерним элементом contain элемента, а это означает, что мы добавляем дополнительный уровень вложенности.

 <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; /* ... */ }

Обратите внимание, что мы не добавляем это значение в более удаленный родительский section и сохраняем контейнер как можно ближе к затронутому элементу.

«Производительность — это искусство избегать работы и делать любую работу максимально эффективной. Во многих случаях речь идет о работе с браузером, а не против него».

— «Производительность рендеринга», Пол Льюис.

Именно поэтому мы должны корректно сигнализировать браузеру об изменении. Обертывание удаленного родительского элемента свойством contain может привести к обратным результатам и отрицательно сказаться на производительности страницы. В наихудших сценариях неправильного использования свойства contain макет может даже сломаться, и браузер не отобразит его правильно.

Запрос контейнера

После того, как в оболочку элемента карты было добавлено свойство contain , мы можем написать контейнерный запрос. Мы contain свойство содержимого к элементу с классом card , так что теперь мы можем включить любой из его дочерних элементов в запрос контейнера.

Как и в случае с обычными медиа-запросами, нам нужно определить запрос, используя свойства min-width или max-width , и вложить все селекторы внутрь блока. Однако мы будем использовать ключевое слово @container вместо @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; } }

И card__wrapper , и элемент card__image являются дочерними элементами элемента card , для которого contain свойство содержимого. Когда мы заменяем обычные медиазапросы контейнерными запросами, удаляем дополнительные классы CSS для узких контейнеров и запускаем пример CodePen в браузере, который поддерживает контейнерные запросы, мы получаем следующий результат.

В этом примере мы изменяем размер не окна просмотра, а самого элемента контейнера <section>, к которому применено свойство CSS resize. Компонент автоматически переключается между макетами в зависимости от размеров контейнера.
В этом примере мы изменяем размер не области просмотра, а самого элемента контейнера <section>, к которому применено свойство CSS resize. Компонент автоматически переключается между макетами в зависимости от размеров контейнера. (Большой превью)

См. Pen [Карточки продуктов: контейнерные запросы] (https://codepen.io/smashingmag/pen/PopmQLV) Адриана Беса.

См. Карточки продуктов Pen: Container Queries, автор Adrian Bece.

Обратите внимание, что контейнерные запросы в настоящее время не отображаются в инструментах разработчика Chrome , что немного усложняет отладку контейнерных запросов. Ожидается, что в будущем в браузер будет добавлена ​​надлежащая поддержка отладки.

Вы можете видеть, как контейнерные запросы позволяют нам создавать более надежные и многократно используемые компоненты пользовательского интерфейса, которые можно адаптировать практически к любому контейнеру и макету. Однако до надлежащей поддержки контейнерных запросов браузерами еще далеко. Давайте попробуем и посмотрим, сможем ли мы реализовать контейнерные запросы, используя прогрессивное улучшение.

Прогрессивное улучшение и полифиллы

Давайте посмотрим, сможем ли мы добавить запасной вариант к варианту класса CSS и медиа-запросам. Мы можем использовать запросы функций CSS с правилом @supports для обнаружения доступных функций браузера. Однако мы не можем проверять другие запросы, поэтому нам нужно добавить проверку для значения contain: layout inline-size style . Придется предположить, что браузеры, поддерживающие свойство inline-size также поддерживают контейнерные запросы.

 /* 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 */ }

Однако такой подход может привести к дублированию стилей, поскольку одни и те же стили применяются как запросом контейнера, так и запросом мультимедиа. Если вы решите реализовать контейнерные запросы с прогрессивным улучшением, вы захотите использовать препроцессор CSS, такой как SASS, или постпроцессор, такой как PostCSS, чтобы избежать дублирования блоков кода и вместо этого использовать миксины CSS или другой подход.

См. Pen [Карты продуктов: контейнерные запросы с прогрессивным улучшением] (https://codepen.io/smashingmag/pen/zYZwRXZ) Адриана Беса.

См. Карточки продуктов Pen: Container Queries с прогрессивным улучшением от Adrian Bece.

Поскольку эта спецификация запроса контейнера все еще находится на экспериментальной стадии, важно помнить, что спецификация или реализация могут измениться в будущих выпусках.

Кроме того, вы можете использовать полифиллы, чтобы обеспечить надежный запасной вариант. Я хотел бы выделить два полифилла JavaScript, которые в настоящее время активно поддерживаются и предоставляют необходимые функции контейнерных запросов:

  • cqfill Джонатан Нил
    Полифил JavaScript для CSS и PostCSS
  • react-container-query Крис Гарсия
    Пользовательский хук и компонент для React

Переход от медиазапросов к контейнерным запросам

Если вы решите внедрить контейнерные запросы в существующий проект, в котором используются мультимедийные запросы, вам потребуется провести рефакторинг кода HTML и CSS. Я обнаружил, что это самый быстрый и простой способ добавления контейнерных запросов, в то же время обеспечивающий надежный запасной вариант для медиа-запросов. Давайте посмотрим на предыдущий пример карты.

 <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 { /* ... */ } }

Во-первых, оберните корневой HTML-элемент, к которому применен медиа-запрос, элементом, имеющим свойство contain .

 <section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
 @supports (contain: inline-size) { .card { contain: layout inline-size style; } }

Затем оберните медиа-запрос в запрос функции и добавьте запрос-контейнер.

 @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 */ } }

Хотя этот метод приводит к некоторому раздуванию кода и дублированию кода, с помощью SASS или PostCSS вы можете избежать дублирования кода разработки, поэтому исходный код CSS остается пригодным для сопровождения.

Как только контейнерные запросы получат надлежащую поддержку браузера, вы можете рассмотреть возможность удаления @supports not (contain: inline-size) и продолжить поддержку исключительно контейнерных запросов.

Стефани Эклс недавно опубликовала замечательную статью о контейнерных запросах, описывающую различные стратегии миграции. Рекомендую ознакомиться с дополнительной информацией по теме.

Сценарии использования

Как мы видели из предыдущих примеров, контейнерные запросы лучше всего использовать для многократно используемых компонентов с макетом, который зависит от доступного пространства контейнера и который можно использовать в различных контекстах и ​​добавлять в разные контейнеры на странице.

Другие примеры включают (примеры требуют браузера, поддерживающего контейнерные запросы):

  • Модульные компоненты, такие как карточки, элементы форм, баннеры и т. д.
  • Адаптируемые макеты
  • Разбивка на страницы с различными функциями для мобильных и настольных компьютеров
  • Увлекательные эксперименты с изменением размера CSS

Заключение

Как только спецификация будет реализована и будет широко поддерживаться в браузерах, контейнерные запросы могут стать функцией, меняющей правила игры. Это позволит разработчикам писать запросы на уровне компонентов, перемещая запросы ближе к связанным компонентам, вместо того, чтобы использовать удаленные и едва связанные медиа-запросы окна просмотра. Это приведет к более надежным, повторно используемым и ремонтопригодным компонентам, которые смогут адаптироваться к различным вариантам использования, макетам и контейнерам.

В настоящее время контейнерные запросы все еще находятся на ранней экспериментальной стадии, и реализация может измениться. Если вы хотите начать использовать контейнерные запросы в своих проектах уже сегодня, вам нужно добавить их с помощью прогрессивного улучшения с обнаружением функций или использовать полифилл JavaScript. Оба случая приведут к некоторым накладным расходам в коде, поэтому, если вы решите использовать контейнерные запросы на этом раннем этапе, обязательно запланируйте рефакторинг кода после того, как функция станет широко поддерживаться.

использованная литература

  • «Контейнерные запросы: краткое руководство», Дэвид А. Херрон
  • «Поздоровайтесь с контейнерными запросами CSS», Ахмад Шадид
  • «Контейнер CSS в Chrome 52», Пол Льюис.
  • «Помощь браузерам в оптимизации с помощью свойства CSS Contain», Рэйчел Эндрю.