От автора: контейнерные запросы CSS приближают медиа-запросы к целевым элементам и позволяют им адаптироваться практически к любому заданному контейнеру или макету. В этой статье мы собираемся рассказать об основах контейнерных запросов CSS и о том, как их использовать сегодня.
Когда мы пишем медиа-запросы для элемента пользовательского интерфейса, мы всегда описываем, как этот элемент стилизован в зависимости от размеров экрана. Этот подход хорошо работает, когда скорость отклика медиа-запроса целевого элемента должна зависеть только от размера области просмотра. Давайте посмотрим на следующий пример адаптивного макета страницы.
Пример адаптивного макета страницы. На левом изображении показан макет рабочего стола с шириной области просмотра 1280 пикселей, а на правом изображении — мобильный макет с шириной области просмотра 568 пикселей.
Однако адаптивный веб-дизайн (RWD) не ограничивается макетом страницы — отдельные компоненты пользовательского интерфейса обычно имеют медиа-запросы, которые могут изменять свой стиль в зависимости от размеров области просмотра.
Пример адаптивного компонента карточного макета. На левом изображении показан компонент с шириной области просмотра 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 или медиа-запросы для разных контекстов и вариантов использования.
Прежде чем мы углубимся в контейнерные запросы, нам нужно проверить поддержку браузера и посмотреть, как мы можем включить экспериментальную функцию в нашем браузере.
Поддержка браузерами
Контейнерные запросы — это экспериментальная функция, доступная в настоящее время в версии Chrome Canary (на момент написания этой статьи). Если вы хотите продолжить и запустить примеры с CodePen в этой статье, вам необходимо включить контейнерные запросы в следующем URL-адресе настроек.
chrome://flags/#enable-container-queries
Если вы используете браузер, который не поддерживает контейнерные запросы, вместе с демонстрацией CodePen будет предоставлено изображение, демонстрирующее предполагаемый рабочий пример.
Работа с контейнерными запросами
Контейнерные запросы не так просты, как обычные медиа-запросы. Нам придется добавить дополнительную строку кода CSS к нашему элементу пользовательского интерфейса, чтобы заствить контейнерные запросы работать, но для этого есть причина, и мы рассмотрим ее позже.
Свойство сontain
Свойство CSS сontain было добавлено в большинство современных браузеров и на момент написания этой статьи и имело приличную поддержку браузеров на уровне 75%. Свойство сontain используется в основном для оптимизации производительности, указывая браузеру, какие части страниц могут рассматриваться как независимые и не будут влиять на изменения других элементов в дереве. Таким образом, если изменение происходит в одном элементе, браузер повторно отобразит только эту часть (поддерево), а не всю страницу. При использовании свойства сontain, мы можем указать, какие типы ограничений мы хотим использовать — layout, size или paint.
Есть много замечательных статей о свойстве сontain, в которых более подробно описаны доступные варианты и варианты использования, поэтому я собираюсь сосредоточиться только на свойствах, связанных с контейнерными запросами.
Какое отношение имеет свойство CSS сontain, используемое для оптимизации, к контейнерным запросам? Для работы контейнерных запросов браузеру необходимо знать, происходит ли изменение в макете дочерних элементов элемента, и он должен повторно отображать только этот компонент. Браузер будет знать, что нужно применить код в контейнерном запросе к соответствующему компоненту, когда компонент визуализируется или его размер изменяется.
Мы будем использовать значения layout и style для свойства contain, но мы также будем нуждаться в дополнительном значении, которое сигнализирует браузеру об оси, в которой будут происходить изменение.
inline-size — Ограничение на встроенной оси. Ожидается, что это значение будет иметь значительно больше вариантов использования, поэтому оно реализуется в первую очередь.
block-size — Ограничение на оси блока. Оно все еще находится в разработке и в настоящее время недоступен.
Одним из незначительных недостатков свойства 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 в браузере, который поддерживает контейнерные запросы, мы получаем следующий результат.
В этом примере мы изменяем размер не области просмотра, а самого элемента контейнера
Обратите внимание, что контейнерные запросы в настоящее время не отображаются в инструментах разработчика 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 или другой подход.
Поскольку эта спецификация контейнерного запроса все еще находится на экспериментальной стадии, важно помнить, что спецификация или реализация могут быть изменены в будущих выпусках.
В качестве альтернативы вы можете использовать полифилы. Я хотел бы выделить два полифила 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) и продолжение поддержки только контейнерных запросов.
Стефани Эклз недавно опубликовала отличную статью о контейнерных запросах, охватывающую различные стратегии миграции. Я рекомендую ознакомиться с ней для получения дополнительной информации по теме.
Сценарии вариантов использования
Как мы видели из предыдущих примеров, контейнерные запросы лучше всего использовать для многократно используемых компонентов с макетом, который зависит от доступного пространства контейнера и может использоваться в различных контекстах и добавляться в разные контейнеры на странице.
Другие примеры по теме (для примеров требуется браузер, поддерживающий контейнерные запросы):
Модульные компоненты, такие как карточки, элементы форм, баннеры и т. д.
Заключение
Как только спецификация будет реализована и получит широкую поддержку в браузерах, контейнерные запросы могут изменить правила игры. Это позволит разработчикам писать запросы на уровне компонентов, вместо использования удаленных и мало связанных медиа-запросов области просмотра. Это приведет к созданию более надежных, многоразовых и обслуживаемых компонентов, которые смогут адаптироваться к различным сценариям использования, макетам и контейнерам.
В настоящее время контейнерные запросы все еще находятся на ранней экспериментальной стадии, и их реализация может быть изменена. Если вы хотите начать использовать контейнерные запросы в своих проектах уже сегодня, вам необходимо добавить их с помощью прогрессивного улучшения с обнаружением функций или использовать полифил JavaScript. Оба случая приведут к некоторому переизбытке кода, поэтому, если вы решите использовать контейнерные запросы на этом раннем этапе, обязательно спланируйте рефакторинг кода, как только эта функция станет широко поддерживаться.
Автор: Adrian Bece
Источник: www.smashingmagazine.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен