До aspect-ratio задача «сделай блок с пропорцией 16:9» решалась через нелепый трюк: пустой блок-обёртка с padding-bottom: 56.25% (это 9 / 16 в процентах), внутрь абсолютно позиционированный реальный контент. Работает, но смотреть страшно: padding-bottom в процентах считается от ширины родителя, и эту магию надо помнить, искать в гугле или копировать из старого проекта.

Свойство aspect-ratio закрывает эту боль ровно одной строчкой: задаём пропорцию — браузер сам вычисляет недостающую сторону по заданной. Никаких обёрток, никакого position: absolute для содержимого. Разберём, как им пользоваться без сюрпризов.

Синтаксис: какие значения принимает

Значения у aspect-ratio три:

  • auto — пропорция не задана. Для обычных блоков это просто «нет соотношения», для замещаемых элементов вроде <img> и <video> — их собственное соотношение сторон (если оно известно).
  • <ratio> — два числа через слеш: 16 / 9, 4 / 3, 1 / 1. Это привычная запись пропорции «ширина к высоте».
  • auto <ratio> — комбинация двух предыдущих: пропорция-резерв, которую браузер использует, пока у замещаемого элемента нет собственной пропорции (картинка ещё не загрузилась). Для обычных блоков работает как обычный <ratio>. Об этой форме отдельный раздел ниже — она и есть самая полезная.

Несколько практических нюансов записи:

.card {
  /* классическое 16:9 */
  aspect-ratio: 16 / 9;
}

.tile {
  /* квадрат — можно так */
  aspect-ratio: 1 / 1;
  /* или короче — без слеша */
  aspect-ratio: 1;
}

.banner {
  /* можно дробным числом — это эквивалент 0.5 / 1, то есть 1:2 */
  aspect-ratio: 0.5;
}

Когда пишем без слеша — второе число неявно равно 1. То есть aspect-ratio: 2 — это 2 / 1 (вдвое шире, чем выше), а не 1 / 2. Если вы любитель чистоты — пишите со слешем всегда, читается понятнее.

Пробелы вокруг слеша свободные: 16/9, 16 / 9 и 16 /9 — всё одно и то же.

Главное правило: одна сторона должна быть auto

Это правило, на которое жалуются чаще всего, и звучит оно нелогично: если задать одновременно и width, и height фиксированными значениями, aspect-ratio молча игнорируется. Никакой ошибки в консоли — просто свойство ничего не делает.

/* работает: высота вычислится автоматически как 100/16*9 = 56.25px */
.ok {
  width: 100px;
  aspect-ratio: 16 / 9;
}

/* работает: ширина вычислится автоматически */
.ok-too {
  height: 100px;
  aspect-ratio: 16 / 9;
}

/* НЕ работает: обе размерности заданы, противоречие, aspect-ratio проигрывает */
.broken {
  width: 100px;
  height: 200px;
  aspect-ratio: 16 / 9;
}

Логика тут такая: aspect-ratio — это правило вычисления, а не более приоритетное значение. Если вы уже задали обе стороны вручную, вычислять нечего и пропорция ничего не делает. Чтобы свойство работало, одна из размерностей должна оставаться auto (это значение по умолчанию, его можно просто не писать).

То же относится к комбинациям с min-*/max-*: если жёсткие ограничители заставят браузер выйти за пропорцию, побеждают они, а не aspect-ratio. Об этом подробнее в разделе про подводные камни.

Главная фишка: auto <ratio> для img и video

Это та форма, ради которой стоит запомнить aspect-ratio, даже если в остальном вёрстка обходится без него.

Когда браузер видит <img>, у него есть два момента, определяющие - знает ли он пропорцию картинки:

  1. До загрузки — не знает. Зарезервированной высоты нет, страница в этом месте занимает 0 пикселей.
  2. После загрузки — знает: пропорция берётся из самой картинки.

Если в момент между этими двумя точками пользователь начал скроллить, контент под картинкой подпрыгнет на её высоту, как только она дорисуется. Это и есть тот самый CLS (Cumulative Layout Shift, накопленный сдвиг макета) — одна из метрик Core Web Vitals, по которой Google штрафует за неудобство для пользователя.

Комбинация auto <ratio> решает это в одну строчку:

img {
  width: 100%;
  height: auto;
  aspect-ratio: auto 16 / 9;
}

Читается так: «используй собственную пропорцию картинки, а пока её не знаешь — считай, что 16:9». До загрузки браузер резервирует ровно столько места, сколько нужно под 16:9-картинку выбранной ширины. Как только src загрузился — реальная пропорция перекрывает резерв, и блок подстраивается. Если реальные пропорции совпали с резервом — никакого сдвига вовсе.

Без auto поведение жёстче: aspect-ratio: 16/9 на <img> заставит браузер натянуть на блок ровно 16:9, даже если реальная картинка 4:3 — она исказится. Чтобы этого не произошло, либо ставьте auto в начале, либо добавляйте object-fit: cover/contain (см. отдельную статью про object-fit).

Полезно знать: атрибуты width и height на самом теге. Современные браузеры (Chrome 79+, Firefox 71+, Safari 14+) автоматически выводят aspect-ratio из HTML-атрибутов width и height у картинки, даже если CSS их перетирает на width: 100%; height: auto. То есть для типичной адаптивной картинки достаточно просто проставить размеры в атрибутах:

<img src="cover.jpg" width="1200" height="675" alt="">

Это эквивалентно aspect-ratio: auto 1200/675 и тоже спасает от CLS. Свойство aspect-ratio в CSS нужно, когда атрибуты по какой-то причине не подходят: размеры неизвестны заранее, картинка приходит из CMS без габаритов, или внутри блока не <img>, а <iframe>/<div>.

Где это реально пригождается

Адаптивный video-эмбед

Раньше для встраивания YouTube-плеера приходилось делать обёртку с padding-bottom: 56.25% и position: absolute на <iframe>. Сейчас — одна строка:

.video-embed {
  width: 100%;
  aspect-ratio: 16 / 9;
}
.video-embed iframe {
  width: 100%;
  height: 100%;
  border: 0;
}

На любой ширине родителя плеер останется в правильной пропорции. Никакой обёртки и магических процентов.

Карточки одинаковой пропорции в гриде

Грид-плитка из карточек, которые должны быть одинаково квадратными, — типовой случай:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
}
.grid .card {
  aspect-ratio: 1;
  border-radius: 12px;
  background: #f1f5f9;
}

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

Превью-аватарки и миниатюры

Список комментариев или каталог авторов, где у каждого аватарка должна быть строго квадратной независимо от исходной картинки:

.avatar {
  width: 64px;
  aspect-ratio: 1;
  border-radius: 50%;
  object-fit: cover;
}

Здесь aspect-ratio диктует форму, а object-fit: cover — как картинка обрезается внутри неё. Без него непропорциональное изображение растянулось бы в овал.

Hero-баннеры с фиксированной пропорцией

Большая верхняя картинка-обложка на странице обычно требует одинакового соотношения сторон на десктопе и мобайле, чтобы не ломать композицию. Раньше это делали через vh-единицы и медиа-запросы, теперь — одной строкой:

.hero {
  width: 100%;
  aspect-ratio: 21 / 9;
  background: url('/storage/hero.jpg') center/cover;
}

На широком экране — кинематографичный баннер 21:9, на мобайле — тот же кадр в той же пропорции, просто меньшего размера.

Подводные камни

Инлайн-элементы и табличные коробки

Свойство применяется ко всем элементам, кроме инлайновых (по умолчанию — <span>, <a>, <em>) и внутренних табличных коробок (display: table-cell, table-row и т. п.). Если хочется задать пропорцию инлайн-ссылке — сначала переведите её в display: inline-block или block, иначе свойство тихо проигнорируется.

Контент длиннее, чем влезает в пропорцию

Самый коварный случай. Задали width: 300px; aspect-ratio: 1 — ожидаете квадрат 300×300. А внутри лежит длинный текст в десять строк. Что будет?

.card {
  width: 300px;
  aspect-ratio: 1;
  border: 1px solid #ccc;
  padding: 16px;
}

По умолчанию — контент вылезет за нижнюю границу. Внешне блок остался 300×300, но текст торчит. Если хочется, чтобы блок рос, как только контент не влез, добавляем min-height: auto (это не значение по умолчанию для блоков с явной высотой):

.card {
  width: 300px;
  aspect-ratio: 1;
  min-height: auto; /* блок может стать выше пропорции */
}

А если, наоборот, хочется обрезать лишнее — добавляем overflow: hidden. Третьего, «незаметно ужать контент», в CSS не предусмотрено — для этого есть отдельные приёмы вроде обрезки многострочного текста.

box-sizing и какой блок считается

Пропорция считается от того блока, который задаёт box-sizing. Если у элемента box-sizing: content-box (значение по умолчанию по спецификации, но в большинстве проектов глобально перекрытое на border-box) — пропорция считается без учёта padding и border. С border-box — включая их. Звучит абстрактно, на практике значит вот что:

* { box-sizing: border-box; }

.card {
  width: 200px;
  padding: 20px;
  aspect-ratio: 1;
}
/* border-box: внешняя коробка 200x200, контент внутри 160x160 */

.card {
  box-sizing: content-box;
  width: 200px;
  padding: 20px;
  aspect-ratio: 1;
}
/* content-box: контент 200x200, внешняя коробка 240x240 — больше, чем хотели */

Если в проекте глобально стоит box-sizing: border-box (а так в подавляющем большинстве проектов) — никаких сюрпризов, пропорция применяется к внешней коробке, которая и есть видимый блок.

object-fit нужен, если внутри картинка

Этот пункт повторим отдельно, потому что забывают часто. aspect-ratio на <img> диктует форму контейнера картинки. А как сама картинка внутри неё себя ведёт — растягивается, обрезается или вписывается — решает object-fit. По умолчанию object-fit: fill, что значит «растяни как угодно», и без коррекции непропорциональная картинка станет визуально кривой.

.avatar {
  width: 64px;
  aspect-ratio: 1;
  object-fit: cover; /* обрезать по центру, не искажая */
}

Это — повторюсь — та же тема, что у комбинации auto <ratio>, но с другого ракурса: auto <ratio> отдаёт пропорцию реальной картинке, а object-fit: cover навязывает форму и подгоняет картинку под неё.

А «padding-bottom»-хак ещё нужен?

Короткий ответ — нет.

Хак с padding-bottom: 56.25% жил в кодовых базах ради одной причины: Internet Explorer. С июня 2022 года IE окончательно мёртв, поддержка aspect-ratio у живых браузеров — 93.9% по caniuse, последний живой браузер без поддержки исчез в 2021 году с релизом Safari 15. Никакой фолбэк не нужен.

Если вы видите padding-bottom-хак в существующем коде — это либо легаси, которое не успели переписать, либо след копипасты со старого Stack Overflow. Переписывать на aspect-ratio можно смело, поведение будет идентичным, а кода вдвое меньше.

С 20 марта 2024 года фича стабильно работает во всех актуальных браузерах. По свежести спеки тоже без сюрпризов: с 2021 года — когда Safari 15 закрыл круг поддержки — синтаксис не менялся, новых форм записи не добавлялось.