Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11

Адаптивные изображения и подписи на CSS: как работают container queries и :has()

Аватарка пользователя Татьяна Жукова
для
Логотип компании Tproger
Tproger
Отредактировано

Как создать адаптивный feature image с подписью без JavaScript? Разбираем, как работают container queries и селектор :has() в CSS, чтобы строить гибкие и живые компоненты под любой экран и контейнер.

2К открытий6К показов
Адаптивные изображения и подписи на CSS: как работают container queries и :has()

Container queries и селектор :has() делают адаптивность вёрстки проще и чище. Разбираем, как с их помощью построить компонент с feature image, который подстраивается под контейнеры, меняет расположение и стиль подписи, оставаясь стабильным при любой ширине.

P.s. Это перевод англоязычного материала, но примеры и принципы легко применимы в ваших проектах.

Введем в курс дела

Когда автор оригинала переделывал сайт, ему понадобилось, чтобы feature image (картинка + подпись) адекватно вела себя в разных контейнерах и при разных размерах экрана.

Что было нужно:

  • На маленьких размерах показывать подпись в классическом виде;
  • Если контейнер достаточно большой, поворачивать изображение и показывать подпись по кругу в правом нижнем углу.

В демо без container queries это выглядело так:

			.figure {
  figcaption {
    /* Классический стиль */
  }

  @media (min-width: 600px) {
    figcaption {
      position: absolute;
      right: 0;
      bottom: 0;
      /* Круговая подпись */
    }
  }
}

		
Адаптивные изображения и подписи на CSS: как работают container queries и :has() 1

В реальности, даже если контент уже вмещался в одну строку, фигура оставалась в классическом виде. Можно ли сделать лучше? Спойлер — да!

Сила container queries

Container queries позволяют переключать стили не в зависимости от вьюпорта, а от размера контейнера, в котором находится компонент.

В демо автор выделил четыре разных состояния размеров, чтобы показать, как с помощью container queries плавно поменять вид с «stacked» (вертикальная компоновка) на «circular» (круговая подпись) и обратно.

С media queries это превратилось бы в лес условий:

			figure {
  /* stacked */

  @media (min-width: 400px) {
    /* circular */
  }

  @media (min-width: 900px) {
    /* stacked */
  }

  @media (min-width: 1100px) {
    /* circular */
  }
}

		

И всё это легко ломается, если вдруг меняется контент или макет.

Адаптивные изображения и подписи на CSS: как работают container queries и :has() 2

Container queries же дают возможность адаптировать вид компонента в зависимости от реальной доступной ширины, без лишних хаков и постоянных правок брейкпоинтов при каждом изменении дизайна.

Как это собрать на практике

1. Оборачиваем компонент в контейнер

Чтобы container queries работали корректно, компонент нужно обернуть в отдельный контейнер и уже относительно него делать запросы. Иначе CSS будет «зацикливаться» и ломаться.

			<div class="figure-wrapper">
  <figure>
    <img src="images/thumb.jpg" alt="" />
    <figcaption></figcaption>
  </figure>
</div>

		

Определяем figure-wrapper как контейнер:

			.figure-wrapper {
  container-type: inline-size;
  container-name: thumbWrapper;
}

		

Простой тест: если ширина контейнера больше 240px, добавляем outline к figure.

			@container thumbWrapper (min-width: 240px) {
  figure {
    outline: dashed 2px deeppink;
    outline-offset: 2px;
  }
}

		

Ресайзим окно браузера — и видим, как рамка появляется при ширине 240px+:

Адаптивные изображения и подписи на CSS: как работают container queries и :has() 3

Условные стили — :has()

По умолчанию компонент в stacked-версии. Если ширины достаточно, переключаемся на circular.

Важно проверить, есть ли у figure — figcaption. Если есть — поворачиваем изображение. Для этого используем селектор :has().

			figure {
  @container thumbWrapper (min-width: 250px) {
    &:has(figcaption) {
      img {
        transform: rotate(1.25deg);
      }
    }
  }
}

		

Базовая структура готова, переходим к верстке.

2. Собираем лейаут

Хотя задача простая, есть несколько рабочих подходов.

Опция 1: Полный position:absolute

Здесь и изображение, и подпись выносятся из потока:

			figure {
  @container thumbWrapper (min-width:240px) {
    position: relative;

    img {
      position: absolute;
      left: 0;
      top: 0;
      width: 90%;
    }
  }
}

		

Но у такого решения контейнер почти не имеет высоты, так как внутри нет элементов в потоке.

Адаптивные изображения и подписи на CSS: как работают container queries и :has() 4

Исправляем, задавая aspect-ratio и ширину контейнеру:

			figure {
  @container thumbWrapper (min-width:240px) {
    position: relative;
    aspect-ratio: 4/3;
    width: 100%;

    img {
      position: absolute;
      left: 0;
      top: 0;
      width: 85%;
    }

    figcaption {
      right: 0;
      bottom: 0;
    }
  }
}

		

Работает, но автор оригинала не в восторге:

  • много position: absolute; 
  • приходится задавать размеры картинкам через проценты, это ломает масштабирование.
Адаптивные изображения и подписи на CSS: как работают container queries и :has() 5

Опция 2: CSS Grid с наложением

Здесь используем CSS Grid, чтобы накладывать изображение и подпись, размещая их в одной ячейке:

			figure {
  display: grid;

  > * {
    grid-area: 1 / -1;
  }
}

		

Результат:

Адаптивные изображения и подписи на CSS: как работают container queries и :has() 6

Далее подгоняем размер изображения, уменьшая его на часть размера подписи:

			figure {
  img {
    max-width: calc(100% - var(--caption-size) / 3);
    max-height: calc(100% - var(--caption-size) / 3);
  }
}

		

Перед нами — аккуратный и гибкий лейаут, который хорошо работает и не требует хакинга.

Адаптивные изображения и подписи на CSS: как работают container queries и :has() 7

Опция 3: Паддинги вместо абсолютов

В этом варианте position: absolute используется только для подписи, а для картинки задаются паддинги, чтобы она не накладывалась на подпись:

			figure {
  @container thumbWrapper (min-width:240px) {
    position: relative;
    padding-inline-end: calc(var(--caption-size) / 3);
    padding-bottom: calc(var(--caption-size) / 3);

    figcaption {
      position: absolute;
      right: 0;
      bottom: 0;
    }
  }
}

		

Автор оригинала выбрал этот вариант для своего блога, но отмечает, что и Grid ничуть не хуже — обе опции удобны и управляемы.

Шаг 3. Делаем компонент «жидким»

Чтобы компонент уверенно работал при любой ширине контейнера и экрана, можно добавить fluid CSS. Настраиваем «жидкость» у следующих параметров:

  • скругления углов;
  • размер шрифта;
  • размер подписи;
  • паддинги.

Благодаря единицам container query, можно адаптировать размеры под ширину контейнера. В данном случае используется cqw (container query width).

			figure {
  /* Размер подписи */
  --caption-size: clamp(6rem, 4.167rem + 13.33cqw, 10rem);

  img {
    border-radius: clamp(0.625rem, -0.625rem + 10cqw, 1.875rem);
  }

  @container thumbWrapper (min-width:240px) {
    padding-inline-end: calc(var(--caption-size) / 4 + 8cqw);
    padding-bottom: calc(var(--caption-size) / 4 + 8cqw);

    figcaption {
      width: var(--caption-size);
      height: var(--caption-size);
      font-size: clamp(0.813rem, 0.727rem + 0.63cqw, 1rem);
    }
  }
}

		

При изменении ширины контейнера меняются скругления, шрифт, паддинги и размер подписи. Так компонент становится адаптивным

Адаптивные изображения и подписи на CSS: как работают container queries и :has() 8

Шаг 4. Тестируем в разных контекстах

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

  • секция с двумя колонками;
  • компонент на всю ширину;
  • сетка с тремя колонками;
  • сетка с двумя колонками, где первый элемент занимает 66% пространства;
  • мобильный размер.

При изменении сеток и ширины компонент продолжает уверенно адаптироваться, не ломая макет.

Адаптивные изображения и подписи на CSS: как работают container queries и :has() 9

Вывод

На первый взгляд компонент может показаться простым, но при использовании в разных контекстах появляются дополнительные вызовы. Современный CSS (container queries, :has(), cqw) позволяет элегантно решать такие задачи, избавляясь от хакинга и лишнего JS.

Следите за новыми постами
Следите за новыми постами по любимым темам
2К открытий6К показов