От автора: сегодня я более взволнован новой функциональностью CSS, чем когда либо за последние шесть лет, которые я провел в качестве фронтенд-разработчика. Прототип контейнерных запросов теперь доступен в Chrome Canary.
Я помню, как видел много шуток о поддержке контейнерных запросов CSS, но, наконец, они есть. В этой статье я расскажу, зачем нам нужны контейнерные запросы, как они упростят вашу жизнь, и, что наиболее важно, вы получите более мощные компоненты и макеты.
Проблема с медиа-запросами CSS
Веб-страница состоит из разных разделов и компонентов, и мы делаем их адаптивными с помощью медиа-запросов CSS. В этом нет ничего плохого, но есть ограничения. Например, мы можем использовать медиа-запрос, чтобы показать минимальную версию компонента на мобильном устройстве по сравнению с настольным компьютером.
Часто отзывчивый веб-дизайн не зависит от области просмотра или размера экрана. Дело в размере контейнера. Рассмотрим следующий пример:
У нас очень типичный макет. Имеется два варианта компонентов: вложенный и горизонтальный.
Есть несколько способов реализовать это в CSS, но наиболее распространенный из них следующий. Нам нужно создать базовый компонент, а затем сделать его вариации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
.c-article { /* The default, stacked version */ } .c-article > * + * { margin-top: 1rem; } /* The horizontal version */ @media (min-width: 46rem) { .c-article--horizontal { display: flex; flex-wrap: wrap; } .c-article > * + * { margin-top: 0; } .c-article__thumb { margin-right: 1rem; } } |
Обратите внимание, что мы создали класс .c-article—horizontal для обработки горизонтальной версии компонента. Если ширина области просмотра больше 46 rem, компонент должен переключиться на горизонтальное отображение.
Это неплохо, но существуют некоторые ограниченным. Я хочу, чтобы компонент реагировал на ширину своего родителя, а не на размер окна просмотра или экрана браузера.
Учтите, что мы хотим использовать значение по умолчанию .c-card в основном разделе. Что случится? Что ж, компонент расширится до ширины свого родительского и, следовательно, будет слишком большой. см. следующий рисунок:
Это проблема, и мы можем решить ее с помощью контейнерных запросов CSS (наконец-то ура). Прежде чем углубиться в них, позвольте мне дать вам представление о желаемом результате.
Нам нужно сообщить компоненту, что если его прямая родительская ширина больше 400 пикселей, то ему необходимо переключиться на горизонтальный стиль. Вот как будет выглядеть CSS:
1 2 3 4 5 6 7 8 9 10 11 12 |
<div class="o-grid"> <div class="o-grid__item"> <article class="c-article"> <!-- content --> </article> </div> <div class="o-grid__item"> <article class="c-article"> <!-- content --> </article> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.o-grid__item { contain: layout inline-size; } .c-article { /* The default style */ } @container (min-width: 400px) { .c-article { /* The styles that will make the article horizontal** ** instead of a card style.. */ } } |
Как нам помогут контейнерные запросы CSS?
Предупреждение: контейнерные запросы CSS пока поддерживаются только в Chrome Canary.
С помощью контейнерных запросов CSS мы можем решить указанную выше проблему и создать гибкий компонент. Это означает, что мы можем добавить компонент в узкий родительский элемент, и он превратится в составную версию, или в более широкий, и тогда он превратится в горизонтальную версию. Опять же, все они не зависят от ширины области просмотра.
Вот как я это себе представляю.
Пурпурный контур представляет ширину родительского элемента. Обратите внимание, как при увеличении его размера, компонент адаптируется к этому. Разве это не круто? В этом сила контейнерных запросов CSS.
Как работают контейнерные запросы
Теперь мы можем поэкспериментировать с контейнерными запросами Chrome Сanary. Чтобы включить возможность контейнерных запросов, введите в адресной строке chrome://flags и выполните поиск по запросу «container queries», а затем включите их.
Первый шаг – добавить свойство contain. Поскольку компонент будет адаптироваться в зависимости от его родительской ширины, нам нужно указать браузеру перерисовывать только затронутую область, а не всю страницу. С помощью свойства contain мы можем сообщить об этом браузеру заранее.
Значение inline-size означает реагирование только на изменение ширины родительского элемента. Я пробовал использовать block-size, но он все еще не работает. Пожалуйста, поправьте меня, если я ошибаюсь.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<div class="o-grid"> <div class="o-grid__item"> <article class="c-article"> <!-- content --> </article> </div> <div class="o-grid__item"> <article class="c-article"> <!-- content --> </article> </div> <!-- other articles.. --> </div> |
1 2 3 |
.o-grid__item { contain: layout inline-size; } |
Это первый шаг. Мы определили элемент .o-grid__item как родительский элемент для .c-article который внутри него. Следующим шагом является добавление стилей, которые мы хотим заставить работать с контейнерными запросами.
1 2 3 4 5 6 7 8 9 10 11 12 |
.o-grid__item { contain: layout inline-size; } @container (min-width: 400px) { .c-article { display: flex; flex-wrap: wrap; } /* other CSS.. */ } |
@container является элементом .o-grid__item, а его ширина min-width: 400px. Мы даже можем пойти дальше и добавить больше стилей. У нас есть следующие стили:
По умолчанию.
Горизонтальная карточка с маленькой миниатюрой.
Горизонтальная карточка с большой миниатюрой.
Если родительский объект слишком велик, стиль будет указать нам, что это избранная статья.
Давайте рассмотрим варианты использования контейнерных запросов CSS.
Примеры использования контейнерных запросов CSS
Контейнерные запросы и CSS-сетка auto-fit
В некоторых случаях использование auto-fit в сетке CSS может привести к неожиданным результатам. Например, компонент будет слишком широким, и его содержимое будет трудночитаемым. Вот наглядное пособие, показывающее разницу между auto-fill и CSS-сеткой auto-fit.
Обратите внимание, что при использовании auto-fit элементы расширяются, чтобы заполнить доступное пространство. Однако в случае auto-fill, элементы сетки не будут расти, и вместо этого у нас будет свободное место (пунктирный элемент справа).
Теперь вы можете спросить, какое отношение это имеет к контейнерным запросам CSS? Что ж, каждый элемент сетки — это контейнер, и когда он расширяется (мы также используем auto-fit), нам нужно, чтобы компонент изменялся в зависимости от контейнера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div class="o-grid"> <div class="o-grid__item"> <article class="c-article"></article> </div> <div class="o-grid__item"> <article class="c-article"></article> </div> <div class="o-grid__item"> <article class="c-article"></article> </div> <div class="o-grid__item"> <article class="c-article"></article> </div> </div> |
1 2 3 4 5 |
.o-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-gap: 1rem; } |
Когда у нас есть четыре элемента, результат должен выглядеть примерно так.
Ситуация изменится, когда количество статей станет меньше, вот что произойдет. Чем меньше у нас предметов, тем они станут шире. Причина в том, что используется auto-fit. Первый выглядит хорошо, но два последних (по 2 в ряду, по 1 в ряду) выглядят не очень хорошо, так как они слишком широкие.
Что, если каждый компонент статьи изменяется в зависимости от ширины его родительского элемента? Таким образом, мы можем извлечь выгоду от использования auto-fit. Вот что нам нужно сделать.
Если ширина элемента сетки больше 400 пикселей, статья должна переключиться на горизонтальный стиль. Вот как мы можем это сделать:
1 2 3 4 5 6 7 8 9 10 |
.o-grid__item { contain: layout inline-size; } @container (min-width: 400px) { .c-article { display: flex; flex-wrap: wrap; } } |
Кроме того, если статья является единственным элементом в сетке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.o-grid__item { contain: layout inline-size; } @container (min-width: 700px) { .c-article { display: flex; justify-content: center; align-items: center; min-height: 350px; } .card__thumb { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: cover; } } |
Вот и все. У нас есть компонент, который реагирует на ширину своего родителя, и он может работать в любом контексте. Разве это не круто? Посмотрите демонстрацию на CodePen.
Боковая и главная панели
Часто нам нужно настроить компонент, чтобы он работал в контейнерах небольшой ширины, таких как <aside>. Для этого идеально подходит вид информационных бюллетеней. Когда ширина мала, нам нужно, чтобы его элементы складывались в стопку, а когда достаточно места, нам нужно, чтобы они разложились по горизонтали.
Как вы видите на рисунке, у нас есть информационный бюллетень, который существует в двух разных контекстах: боковой раздел и основной раздел.
Без контейнерных запросов это невозможно, пока у нас не будет класса вариаций в CSS, например, .newsletter—stacked или что-то в этом роде.
Я знаю, что мы можем принудительно обернуть элементы, если во флексбоксе недостаточно места, но этого недостаточно. Мне нужно гораздо больше контроля, чтобы делать такие вещи, как:
Скрыть определенные элементы.
Сделать кнопку полной ширины.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
.newsletter-wrapper { contain: layout inline-size; } /* The default, stacked version */ .newsletter { /* CSS styles */ } .newsletter__title { font-size: 1rem; } .newsletter__desc { display: none; } /* The horizontal version */ @container (min-width: 600px) { .newsletter { display: flex; justify-content: space-between; align-items: center; } .newsletter__title { font-size: 1.5rem; } .newsletter__desc { display: block; } } |
Посмотрите демонстрацию на CodePen.
Пагинация
Я обнаружил, что разбиение на страницы хорошо подходит для использования контейнерных запросов. Изначально у нас могут быть кнопки «Назад» и «Далее», затем мы можем скрыть их и показать полную разбивку на страницы, если есть достаточно места. Рассмотрим следующий рисунок.
Чтобы обработать состояния, указанные выше, нам нужно сначала поработать со стилем по умолчанию (сложенные кнопки), а затем работать с двумя другими состояниями.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
.wrapper { contain: layout inline-size; } @container (min-width: 250px) { .pagination { display: flex; flex-wrap: wrap; gap: 0.5rem; } .pagination li:not(:last-child) { margin-bottom: 0; } } @container (min-width: 500px) { .pagination { justify-content: center; } .pagination__item:not(.btn) { display: block; } .pagination__item.btn { display: none; } } |
Посмотрите демонстрацию на CodePen.
Карточка профиля
Вот еще один вариант, который идеально подходит для использования в нескольких контекстах. Маленькое состояние работает для небольших размеров области просмотра и контекстов, таких как боковая панель. Большое состояние работает в гораздо более широком контексте, например, при размещении ее в сетке с двумя столбцами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.p-card-wrapper { contain: layout inline-size; } .p-card { /* Default styles */ } @container (min-width: 450px) { .meta { display: flex; justify-content: center; gap: 2rem; border-top: 1px solid #e8e8e8; background-color: #f9f9f9; padding: 1.5rem 1rem; margin: 1rem -1rem -1rem; } /* and other styles */ } |
При этом мы можем увидеть компоненты в разных контекстах без использования единого медиа-запроса.
Посмотрите демонстрацию на CodePen.
Элементы формы
Я еще не углублялся в варианты использования форм, но задумал переключиться с горизонтальных меток на штабелированные.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.form-item { contain: layout inline-size; } .input-group { @container (min-width: 350px) { display: flex; align-items: center; gap: 1.5rem; input { flex: 1; } } } |
Попробуйте сами в демонстрации ниже. Посмотрите демонстрацию на CodePen.
Компоненты тестирования
Теперь, когда мы изучили пару примеров использования, в которых контейнерные запросы CSS могут быть полезны, как мы можем протестировать компонент? К счастью, мы можем сделать это с помощью свойства CSS resize в родительском компоненте.
1 2 3 4 5 |
.parent { contain: layout inline-size; resize: horizontal; overflow: auto; } |
Отладка контейнерных запросов в DevTools
Пока что вы не можете увидеть что-то вроде @container (min-width: value). Думаю, это вопрос времени, и это будет реализовано.
Заключение
Мне понравилось изучать контейнерные запросы CSS и экспериментировать с ними в браузере. Я знаю, что они еще официально не поддерживаются, но сейчас самое время начать работать с ними.
Часть нашей работы как фронт-энд разработчиков — тестировать и помогать людям, которые работают над реализацией таких функций. Чем больше мы тестируем, тем меньше проблем мы увидим, когда он будет поддерживаться во всех основных браузерах. Спасибо за чтение.
Автор: Ahmad Shadeed
Источник: ishadeed.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен