Современные способы построения адаптивных макетов

CSS в наше время становится всё более мощным. У нас есть отличная поддержка переменных CSS, флексов и гридов. Новейшие возможности, такие как селектор :has и контейнерные запросы, поддерживаются в последних версиях браузеров (почти).

Это означает, что будущее адаптивного дизайна изменится. Речь будет идти не о том, чтобы сделать адаптивной всю страницу. Вместо этого мы будем писать адаптивный CSS для компонентов и позволять браузеру самому решать, когда компонент должен иметь определённый стиль.

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

Флексы

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

Это мощный инструмент, который поможет в создании основы для адаптивных компонентов.

Создание макета компонента Статья

В этом примере у нас есть компонент «карточка», в котором изображение располагается слева, а содержимое справа.

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

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

.c-card {
  display: flex;
}

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

.c-card {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
}

Мы вернулись к тому же исходному результату! Ничего страшного, сейчас это поправим.

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

Далее нам нужно указать браузеру, когда делать перенос элементов с помощью мощного свойства flex.

.c-article__thumb {
  flex: 1 1 550px;
}
.c-article__content {
  flex: 1 1 350px;
}

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

Видите это? Адаптивный дизайн больше не связан с медиа-запросами.

.c-card {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
}

.c-article__thumb {
  flex: 1 1 550px;
}

.c-article__content {
  flex: 1 1 350px;
}

Мы также можем использовать значение wrap-reverse, чтобы изменить положение миниатюры по отношению к содержимому.

.c-card {
  display: flex;
  flex-wrap: wrap-reverse;
  align-items: flex-start;
}

Данный подход можно использовать не только для карточек, но и всего остального.

Заголовок раздела

Крис Койер в своей замечательной статье назвал это Alignment Shifting Wrapping. Рассмотрим следующий пример, вдохновлённый статьёй Криса.

У нас есть раздел с заголовком, который содержит название и ссылку.

Когда места не хватает, мы хотим чтобы заголовок перенёсся на новую строку. Вот всё, что нам нужно:

.section-header {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.section-header__title {
  flex: 1 1 400px;
}

Значение 400px для заголовка — это пользовательская контрольная точка, которая обеспечивает перенос. Как только ширина заголовка составит 400px или меньше, он перенесётся на новую строку.

С помощью медиа-запроса это можно сделать следующим образом:

@media (min-width: 650px) {
  .section-header {
    display: flex;
    /* Перенос не требуется */
  }
}

Всё это будет хорошо работать до тех пор, пока нам не понадобится сделать перенос заголовка раздела. Например, в основном разделе и в боковой панели.

С помощью flex-wrap заголовок раздела может работать даже при использовании в небольших контейнерах, таких как <aside>.

Если элементы окажутся слишком близко друг к другу, то они динамически перенесутся, и в макете не произойдёт ничего странного.

В медиа-запросах нам потребуется использовать дополнительный класс, чтобы нацелиться на заголовок раздела внутри элемента <aside>.

@media (min-width: 800px) {
  .section-header--aside {
    display: flex;
    /* Перенос не требуется */
  }
}

Я считаю это хаком, потому что оно не будет работать во всех случаях. Как только мы изменим ширину <aside>, всё это может сломаться.

Примечание: это хорошо решается с помощью контейнерных запросов, о которых я расскажу позже.

Гриды

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

Рассмотрим следующий пример.

Это пример из моей статьи CSS Grid Template Areas In Action — я взял его из проекта, над которым работал несколько лет назад. Команде нужно было создать две версии макета, и она не смогла придумать ничего лучше, чем использовать гриды.

<div class="c-newspaper">
  <article class="c-article c-article--1"></article>
  <article class="c-article c-article--2"></article>
  <article class="c-article c-article--featured"></article>
  <article class="c-article c-article--3"></article>
  <article class="c-article c-article--4"></article>
  <article class="c-article c-article--5"></article>
  <article class="c-article c-article--6"></article>
  <article class="c-article c-article--7"></article>
</div>
.c-newspaper {
  display: grid;
  grid-template-columns: 0.2fr 0.6fr 0.2fr;
  grid-template-areas:
    "item-1 featured item-2"
    "item-3 featured item-4"
    "item-5 item-6 item-7";
  grid-gap: 1rem;
}

.c-article--1 {
  grid-area: item-1;
}

.c-article--2 {
  grid-area: item-2;
}
/* И так для каждого элемента сетки… */
.c-article--7 {
  grid-area: item-7;
}

.c-article--featured {
  grid-area: featured;
}

Для второго варианта нужно лишь изменить grid-template-areas.

.c-newspaper.variation-1 {
  grid-template-areas:
    "featured featured item-3"
    "item-1 item-2 item-4"
    "item-5 item-6 item-7";
}

На небольших экранах нам понадобится медиа-запрос для изменения grid-template-areas, но это очень мощный инструмент. Немного подработав, мы можем создать неограниченный набор вариантов для этого редакторского макета.

Ещё одна полезная особенность гридов — это функция minmax(). Вкратце, она позволяет нам создать сетку, которая динамически меняет ширину колонок без каких-либо медиа-запросов.

Рассмотрим следующий CSS.

.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  grid-gap: 1rem;
}

У нас есть сетка с тремя колонками, и мы хотим, чтобы они меняли свой размер при уменьшении ширины области просмотра. Функция minmax() в сочетании с auto-fill подходит для этого идеально.

Без функции minmax() у нас нет других вариантов, кроме как использовать медиа-запросы для изменения колонок в зависимости от ширины области просмотра.

Простой пример:

@media (min-width: 992px)
  .wrapper__item {
    width: 33%;
  }
}

Подробнее о minmax() вы можете узнать из моей статьи.

Гибкие размеры

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

h2 {
  font-size: calc(1rem + 5vw);
}

/* Если ширина области просмотра составляет 2000 пикселей или более, 
* ограничить размер шрифта до 4rem.
*/
@media (min-width: 2000px) {
  font-size: 4rem;
}

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

Рассмотрим следующий пример.

h2 {
  font-size: clamp(1rem, 0.5rem + 2.5vw, 3rem);
}

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

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

В былые времена я делал нечто подобное.

h2 {
  font-size: 1rem;
}

@media (min-width: 800px) {
  h2 {
    font-size: 2.5rem;
  }
}

@media (min-width: 1400px) {
  h2 {
    font-size: 5rem;
  }
}

С гибкими размерами мы перестанем думать о фиксированных значениях и перейдём к плавным. Я представляю себе это как указать браузеру минимальное и максимальное значения и дать ему возможность делать остальную работу.

Давайте рассмотрим несколько примеров, в которых гибкие размеры действительно эффективны.

Динамический интервал

С помощью свойства gap можно создать динамический интервал, меняющийся в зависимости от размера области просмотра или контейнера.

.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: clamp(1rem, 2vw, 24px);
}

Для главного раздела нам может понадобиться изменить вертикальный padding в зависимости от размера области просмотра. Для этого идеально подходит стилевая функция clamp() с единицами области просмотра.

.hero {
  padding: clamp(2rem, 10vmax, 10rem) 1rem;
}

Автор: Ахмад Шадид
Последнее изменение: 22.02.2024