aspect-ratio [quirksmode]

Рассмотрим новое объявление aspect-ratio (соотношение сторон) и его использование. Una Kravets написала вводную статью, но следует отметить некоторые дополнительные технические моменты. В конце будет предложен небольшой запасной вариант, который можно использовать, если вам прямо сейчас нужно пользоваться свойством aspect-ratio.

На момент написания aspect-ratio поддерживается в Chrome 90, Safari Technology Preview и Firefox 88, если установить флаг aspect-ratio в about: config. Вам понадобится один из этих браузеров, чтобы увидеть примеры ниже, за исключением запасного, который должен работать во всех браузерах, поддерживающих настраиваемые свойства.

Слабая декларация

Значение CSS-свойства aspect-ratio — слабая декларация. Если у контейнера указаны ширина и высота, браузер будет использовать эти значения и проигнорирует указанное значение соотношения сторон. Ширина и высота могут декларироваться с помощью явных значений width и height или иными способами.

aspect-ratio

В этом примере для всех контейнеров в CSS указано соотношение сторон 16:9 — aspect-ratio: 16/9. Для первого контейнера ширина и высота не указаны (width: auto; height: auto; т.е. ширина и высота в обычных условиях будут определяться содержимым). Тогда свойство aspect-ratio возьмёт фактическую ширину в пикселях и применит указанное в значении соотношение сторон для вычисления высоты.

Для второго контейнера указана высота height: 50px;, ширина по-прежнему не определена (width: auto;). Высота усилилась, но автоматическая ширина всё ещё слаба и позволяет свойству aspect-ratio переопределить её. Таким образом, теперь ширина поля рассчитывается исходя из значений высоты (height) и соотношения сторон (aspect-ratio: 16/9).

Для третьего контейнера указаны фиксированные значения ширины width: 150px; и высоты height: 100px;, а также соотношение сторон aspect-ratio: 16/9. Но теперь и ширина, и высота усилены, поэтому значение 16/9 свойства aspect-ratio будет игнорироваться и указанное соотношение сторон не будет применяться.

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

Округление

Даже когда aspect-ratio работает нормально, браузер должен вычислить целое число пикселей устройства для ширины и высоты контейнера. Дробные части отсекаются, поэтому рассчитанное соотношение сторон контейнера не может бывает точным на 100%.

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

Однако жирные красные полосы, как в последнем квадрате в первом примере, указывают на наличие проблемы с вычислением aspect-ratio.

min- и max-width и -height

grid aspect-ratio и min- и max-height

Вы можете указать min/max-width/height (минимальные/максимальные размеры) для контейнеров. Они применятся, как обычно и указанное значение aspect-ratio тоже будет применено. В этом примере у первого контейнера min-height: 100px, у второго — max-height: 50px, а у третьего — min-width: 100px. Как видно (подвигайте слайдер), они растягиваются вверх или вниз до определенного максимума и минимума и при этом соблюдают указанное для них значение соотношения сторон.

box-sizing: border-box

Теперь мы подобрались к более сложной теме, изначально открытой Ana Tudor — и всю эту статью лучше прочитать.

Когда aspect-ratio работает нормально, ширина или высота контейнера будет изменяться, чтобы соответствовать другой стороне и указанному значению соотношения сторон. Однако точный эффект будет зависеть от того, как реальные ширина и высота определяются свойством box-sizing.

width может возвращать ширину только содержимого без отступов (padding) и границы (при box-sizing: content-box; по умолчанию) или ширину контента + padding + border (при box-sizing: border-box). В общем, последнее — то, что нужно.

aspect-ratio и box-sizing

В следующем примере контейнерам добавлены отступы: padding:10%. Процентное значение всегда рассчитывается относительно ширины родительского элемента. Таким образом, у этих контейнеров будут отступы в 10% от ширины их родительских элементов, даже сверху и снизу.

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

Эту проблему легко решить, если объявить box-sizing: border-box. Теперь ширина и высота определяют всё вместе: контент, отступы и границу. Всей этой области будет задано правильное соотношение сторон. Таким образом, отступы (padding) легко интегрируются с правильным соотношением сторон.

Вообще, правильнее всегда устанавливать box-sizing: border-box для всего сайта, это сильно упрощает работу с размерами элементов, и как правило устраняет ряд подводных камней. Значение content-box было ошибкой, как признал сам W3C (еще в 2002 году). Тот факт, что он влияет на aspect-ratio, просто ещё раз это подтверждает.

apect-ratio в flexbox

В flex- или grid-контексте может показаться, что aspect-ratio не работает. Фактически, именно эти проблемы и побудили написать эту статью.

Посмотрите на пример ниже. Не работает! О, паника! Что происходит?

flex aspect-ratio

Здесь происходит нормальное поведение flexbox по умолчанию. Сначала рассчитывается ширина элементов (исходя из установленных flex-base: 30%; grow: 1), а после этого устанавливается высота всех элементов. Эти высоты рассчитываются путем применения соотношения их сторон, но самый высокий контейнер используется для установки высоты всех элементов в строке. В примере выше это контейнер 1/1, поэтому у остальных контейнеров 16/9 и 4/3 такое же соотношение сторон 1/1.

По умолчанию для flexbox таким поведением управляет align-items: stretch.

Если будете использовать любое другое значение, высота блоков будет установлена автоматически и у них будет правильное соотношение сторон. flex-start — наиболее очевидный выбор, а дополнительные параметры можно найти в отличном руководстве по flexbox на CSS Tricks.

flex aspect-ratio и align-items

Если по какой-то причине потребуется отменить растяжение по высоте только для одного элемента, для него можно указать align-self: flex-start или любое другое значение, либо height: min-content. Оба варианта будут работать нормально. Для контейнера 16/9 в примере ниже указано height: min-content.

flex, aspect-ratio и height: min-content

aspect-ratio и grid

В grid-контексте aspect-ratio сталкивается с теми же проблемами, что и в среде flexbox, только еще и с ошибками браузера. Можно ожидать такого же поведения, что и в контексте flexbox, но на деле происходит не совсем то же самое.

Хорошая новость в том, что align-items, align-self и height: min-content работают точно так же, как с flexbox. Они отменяют стандартное поведение для grid, когда высота всех элементов в строке растягивается до самого высокого из них.

grid, aspect-ratio и align-items

Проблемы заключаются в рендеринге по умолчанию с aspect-ratio. Chrome и Safari реализуют это одним неправильным способом, а Firefox — совершенно другим способом, но тоже неправильным.

Если у всех контейнеров в приведенном ниже примере будет одинаковая ширина и высота, это устранит ошибки. Одинаковая высота у них по причинам, которые объяснялись выше в разделе flexbox, а grid в этом отношении (можно надеяться) работает так же.

grid aspect-ratio с багами браузера

Firefox подчиняется aspect-ratio, но скорее всего — нет. Вместо этого, как в flexbox-примере, он должен растягивать блоки до высоты самого высокого в строке. Вдобавок он вычисляет высоту всей сетки, предполагая, что у её элементов есть минимальная высота, необходимая для их содержимого, но во всех примерах выше нет контента, т.е. его высота равна 0. Таким образом, grid-контейнер слишком мал. Оба случая явно ошибочны, и вероятно, скоро будут исправлены.

Chrome и Safari TP правильно определяют размер контейнера в grid, но похоже, принимают высоту в качестве справочной и соответственно изменяют ширину, вместо того, чтобы основываясь на ширине, изменять высоту. Таким образом, элементы 16/9 и 4/3 становятся слишком широкими. Вероятно, это тоже ошибка, а если это не так, требуются пояснения, что тут происходит. К счастью, эта ошибка исчезает, если как обычно, использовать align-items.

Запасной вариант

«Написав этот запасной вариант, я обнаружил, что Ana Tudor написала примерно то же самое в своей статье. Я имею в виду, почему я вообще пытаюсь соревноваться с такими ужасно умными людьми, как она? Но я пришел к этому самостоятельно, честно.» — Peter-Paul Koch

Поскольку поддержка браузеров ещё не совсем завершена, придётся продолжать использовать запасной вариант — старый трюк с padding-top, как сделано в этом абзаце. Предполагается, что у него будет соотношение сторон даже в браузерах, которые не поддерживают aspect-ratio.

Ядро запасного варианта — это CSS, написанный ниже. Справедливое предупреждение: это решение только слегка протестировано; от замысла к успешному выполнению потрачено примерно 30 минут, хотя ещё 90 минут ушло на проблему с настраиваемыми свойствами.

Дополнительный <span> уродлив, но необходим. Если ваши контейнеры с пропорциональным соотношением сторон не содержат текста, вы можете его убрать.

<p class="test"><span>Текст, текст, текст.</span></p>

p.test {
  --aspectRatio: 16/9;
  --padding: 0.5em;
  border: 1px solid;
  padding: var(--padding);
  aspect-ratio: var(--aspectRatio);
  box-sizing: border-box;
}

@supports not (aspect-ratio: 16/9) {
  p.test {
    padding: 0;
    padding-top: calc((1 / (var(--aspectRatio)))*100%);
    position: relative;
  }
	
  p.test > span {
    display: block;
    position: absolute;
    top: var(--padding);
    left: var(--padding);
  }
}

Сохраните желаемое соотношение сторон в переменной --aspectRatio. Установите это значение свойству aspect-ratio. Если браузер не поддерживает aspect-ratio, установите для padding-top значение 1 / --aspectRatio в процентах. Сценарий, выполняемый на этой странице, изменяет значение CSS-переменной --aspectRatio.

Хитрость здесь в том, что процентное значение рассчитывается относительно ширины родительского элемента. Если блок занимает всю ширину своего родителя, вы можете взять эту ширину, умножить ее на 1, разделенное на соотношение сторон (например, 9/16, когда соотношение сторон составляет 16/9) и преобразовать результат в проценты. Теперь отступ растягивает прямоугольник до желаемого соотношения сторон.

Если в блоке есть реальное содержимое, его придётся обернуть дополнительным HTML-элементом и присвоить этому элементу position: absolute, чтобы он не влиял на высоту блока. Затем его надо разместить в контейнере с координатами сверху и слева, равными значению padding. Теперь текст кажется естественным. Этот трюк не понадобится, если в поле есть только фоновое изображение или градиент; они в любом случае игнорируют отступы (padding).

Или можно подождать несколько месяцев, когда все браузеры будут поддерживать aspect-ratio. Это не займет много времени.

А о невероятном количестве скобок, которые нужны в строке с padding-top можно вообще написать отдельную статью.