Гибкая адаптивная типографика с CSS Poly Fluid Sizing
Опубликовано: 2022-03-10В этой статье мы собираемся перейти на другой уровень. Мы собираемся изучить , как создать масштабируемую, гибкую типографику с несколькими контрольными точками и предопределенными размерами шрифта, используя хорошо поддерживаемые функции браузера и немного базовой алгебры. Самое приятное то, что вы можете автоматизировать все это с помощью Sass.
Дальнейшее чтение на SmashingMag:
- По-настоящему плавная типографика с единицами vh и vw
- Типографские шаблоны в дизайне электронной рассылки в формате HTML
- Хорошие, плохие и отличные примеры веб-типографики
- Инструменты и ресурсы для более значимой веб-типографики
При работе с креативными дизайнерами над дизайном веб-страниц довольно часто приходится получать несколько монтажных областей/макетов Sketch или Photoshop, по одной для каждой контрольной точки. В этом дизайне элементы (например, заголовок h1
) обычно имеют разные размеры в каждой контрольной точке. Например:
-
h1
на маленьком макете может быть22px
-
h1
на среднем макете может быть24px
-
h1
на большом макете может быть34px
Минимум CSS для этого использует медиа-запросы:
h1 { font-size: 22px; } @media (min-width:576px) { h1 { font-size: 22px; } } @media (min-width:768px) { h1 { font-size: 24px; } } @media (min-width:992px) { h1 { font-size: 34px; } }

Это хороший первый шаг, но вы ограничиваете font-size
только тем, что было указано дизайнером в предоставленных контрольных точках. Что бы сказал дизайнер, если бы вы спросили: «Каким должен быть font-size
при ширине окна просмотра 850 пикселей?» Ответ в большинстве случаев заключается в том, что это будет где-то между 24px и 34px. Но прямо сейчас это всего 24 пикселя согласно вашему CSS, что, вероятно, не то, что предполагал дизайнер.
На этом этапе вы можете рассчитать, каким должен быть этот размер, и добавить еще одну точку останова. Это достаточно легко. А как же все остальные разрешения? Какой должен быть font-size
шириной 800 пикселей? Как насчет 900px? А как насчет 935px? Очевидно, что дизайнер не собирается предоставлять вам полный макет для каждого возможного разрешения. Даже если бы они это сделали, должны ли вы добавлять десятки (или сотни) точек останова для всех различных font-sizes
, которые нужны дизайнеру? Конечно, нет.
Ваш макет уже плавно масштабируется по ширине окна просмотра. Было бы неплохо, если бы ваша типографика предсказуемо масштабировалась с вашим плавным макетом? Что еще мы можем сделать, чтобы улучшить это?
Единицы просмотра спешат на помощь?
Единицы области просмотра — еще один шаг в правильном направлении. Они позволяют плавно изменять размер текста в соответствии с вашими макетами. И поддержка браузера великолепна в эти дни.

Но жизнеспособность единиц Viewport очень зависит от оригинального творческого дизайна веб-страницы. Было бы здорово просто установить font-size
с помощью vw
и все готово:
h1 { font-size: 2vw; }

Но это работает только в том случае, если ваши креативные артборды учитывают это. Выбрал ли дизайнер размер текста, равный ровно 2% ширины каждой из его артбордов? Конечно, нет. Давайте посчитаем, какое значение vw
должно быть для каждой из наших точек останова:
22px
22 пикселя при 576px
576 пикселей = 22 ⁄ 576 * 100 = 3,82 24px
Размер 24 пикселя при 768px
768 пикселей = 24 ⁄ 768 * 100 = 3,13 34px
Размер 34 пикселя при 992px
992 пикселя = 34 ⁄ 992 * 100 = 3,43 vw
Они близки, но не все одинаковы. Таким образом, вам все равно нужно будет использовать медиа-запросы для перехода между размерами текста, и все равно будут скачки. И рассмотрим этот странный побочный эффект:
@ 767 пикселей, 3,82% ширины окна просмотра составляет 29 пикселей. Если окно просмотра становится на 1 пиксель шире, font-size
резко падает до 24 пикселей. Эта анимация изменения размера окна просмотра демонстрирует этот нежелательный побочный эффект:

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

font-size
и соответствующей ширины области просмотра (таблицы Google) (просмотреть большую версию) Здесь вы можете увидеть точечную диаграмму заданных дизайнером размеров текста при заданной ширине области просмотра. Ось X — это ширина области просмотра, а ось Y — font-size
. Видите эту линию? Это называется линией тренда . Это способ найти интерполированное значение font-size
для любой ширины окна просмотра на основе предоставленных данных.
Линия тренда — ключ ко всему этому
Если бы вы могли установить font-size
в соответствии с этой линией тренда, у вас был бы h1, который плавно масштабируется на всех разрешениях, которые будут близки к тому, что задумал дизайнер. Во-первых, давайте посмотрим на математику. Прямая линия определяется этим уравнением:

- м = уклон
- b = точка пересечения с осью y
- x = текущая ширина области просмотра
- y = результирующий
font-size
Существует несколько методов определения наклона и точки пересечения по оси Y. Когда задействовано несколько значений, распространенным методом является подбор наименьших квадратов:

После того, как вы выполните эти расчеты, у вас будет уравнение линии тренда.
Как мне использовать это в CSS?
Хорошо, это становится довольно тяжелым для математики. Как мы на самом деле используем этот материал во фронтенд-разработке? Ответ CSS calc()
! Еще раз, довольно новая технология CSS, которая очень хорошо поддерживается.

Вы можете использовать уравнение линии тренда следующим образом:
h1 { font-size: calc({slope}*100vw + {y-intercept}px); }
Как только вы найдете свой наклон и y-перехват, вы просто подключите их!
Примечание. Вы должны умножить наклон на 100
, так как вы используете его как единицу vw
, которая составляет 1/100 ширины области просмотра.
Можно ли это автоматизировать?
Я перенес метод наименьших квадратов в простую в использовании функцию Sass:
/// least-squares-fit /// Calculate the least square fit linear regression of provided values /// @param {map} $map - A Sass map of viewport width and size value combinations /// @return Linear equation as a calc() function /// @example /// font-size: least-squares-fit((576px: 24px, 768px: 24px, 992px: 34px)); /// @author Jake Wilson <[email protected]> @function least-squares-fit($map) { // Get the number of provided breakpoints $length: length(map-keys($map)); // Error if the number of breakpoints is < 2 @if ($length < 2) { @error "leastSquaresFit() $map must be at least 2 values" } // Calculate the Means $resTotal: 0; $valueTotal: 0; @each $res, $value in $map { $resTotal: $resTotal + $res; $valueTotal: $valueTotal + $value; } $resMean: $resTotal/$length; $valueMean: $valueTotal/$length; // Calculate some other stuff $multipliedDiff: 0; $squaredDiff: 0; @each $res, $value in $map { // Differences from means $resDiff: $res - $resMean; $valueDiff: $value - $valueMean; // Sum of multiplied differences $multipliedDiff: $multipliedDiff + ($resDiff * $valueDiff); // Sum of squared resolution differences $squaredDiff: $squaredDiff + ($resDiff * $resDiff); } // Calculate the Slope $m: $multipliedDiff / $squaredDiff; // Calculate the Y-Intercept $b: $valueMean - ($m * $resMean); // Return the CSS calc equation @return calc(#{$m*100}vw + #{$b}); }
Это действительно работает? Откройте этот CodePen и измените размер окна браузера. Оно работает! Размеры шрифта довольно близки к тому, что требовалось в исходном дизайне, и они плавно масштабируются с вашим макетом.

Тест SCSS с наименьшими квадратами подходит user="jakobud"] См. тест Pen Least Squares Fit SCSS от Джейка Уилсона (@jakobud) на CodePen.
Теперь, по общему признанию, это не идеально. Значения близки к исходному дизайну, но не совсем совпадают. Это связано с тем, что линейная линия тренда является аппроксимацией определенных размеров шрифта при определенной ширине области просмотра. Это унаследовано от линейной регрессии. В ваших результатах всегда есть какая-то ошибка. Это компромисс между простотой и точностью. Кроме того, имейте в виду, что чем более разнообразны размеры вашего текста, тем больше ошибок будет в вашей линии тренда.
Можем ли мы сделать лучше, чем это?
Подгонка полиномиального метода наименьших квадратов
Чтобы получить более точную линию тренда, вам нужно изучить более сложные темы, например, линию тренда полиномиальной регрессии, которая может выглядеть примерно так:

Вот это больше похоже! Гораздо точнее, чем наша прямая. Основное уравнение полиномиальной регрессии выглядит следующим образом:

Чем точнее вы хотите получить кривую, тем сложнее становится уравнение. К сожалению, вы не можете сделать это в CSS . calc()
просто не может выполнять сложную математику такого типа. В частности, вы не можете вычислять показатели:
font-size: calc(3vw * 3vw); /* This doesn't work in CSS */
Так что, пока calc()
не поддерживает этот тип нелинейной математики, мы застряли только с линейными уравнениями . Что еще мы можем сделать, чтобы улучшить это?
Точки останова и множественные линейные уравнения
Что, если бы мы вычисляли только прямую линию между каждой парой точек останова? Что-то вроде этого:

Таким образом, в этом примере мы будем вычислять прямую линию между 22px
и 24 24px
, а затем другую между 24px
и 34px
. Сасс будет выглядеть так:
// SCSS h1 { @media (min-width:576px) { font-size: calc(???); } @media (min-width:768px) { font-size: calc(???); } }
Мы могли бы использовать метод наименьших квадратов для этих значений calc()
, но поскольку это просто прямая линия между двумя точками, математика может быть значительно упрощена. Помните уравнение прямой линии?

Поскольку сейчас мы говорим только о двух точках, найти наклон (m) и точку пересечения с осью y (b) несложно:

Вот функция Sass для этого:
/// linear-interpolation /// Calculate the definition of a line between two points /// @param $map - A Sass map of viewport widths and size value pairs /// @returns A linear equation as a calc() function /// @example /// font-size: linear-interpolation((320px: 18px, 768px: 26px)); /// @author Jake Wilson <[email protected]> @function linear-interpolation($map) { $keys: map-keys($map); @if (length($keys) != 2) { @error "linear-interpolation() $map must be exactly 2 values"; } // The slope $m: (map-get($map, nth($keys, 2)) - map-get($map, nth($keys, 1)))/(nth($keys, 2) - nth($keys,1)); // The y-intercept $b: map-get($map, nth($keys, 1)) - $m * nth($keys, 1); // Determine if the sign should be positive or negative $sign: "+"; @if ($b < 0) { $sign: "-"; $b: abs($b); } @return calc(#{$m*100}vw #{$sign} #{$b}); }
Теперь просто используйте функцию линейной интерполяции на нескольких точках останова в вашем Sass. Кроме того, давайте добавим некоторые минимальные и максимальные font-sizes
:
// SCSS h1 { // Minimum font-size font-size: 22px; // Font-size between 576 - 768 @media (min-width:576px) { $map: (576px: 22px, 768px: 24px); font-size: linear-interpolation($map); } // Font-size between 768 - 992 @media (min-width:768px) { $map: (768px: 24px, 992px: 34px); font-size: linear-interpolation($map); } // Maximum font-size @media (min-width:992px) { font-size: 34px; } }
И он генерирует этот CSS:
h1 { font-size: 22px; } @media (min-width: 576px) { h1 { font-size: calc(1.04166667vw + 16px); } } @media (min-width: 768px) { h1 { font-size: calc(4.46428571vw - 10.28571429px); } } @media (min-width: 992px) { h1 { font-size: 34px; } }

Святой Грааль размера CSS?
Давайте завернем все это в хороший миксин Sass (для ленивых и эффективных!). Я придумываю этот метод Poly Fluid Sizing :
/// poly-fluid-sizing /// Generate linear interpolated size values through multiple break points /// @param $property - A string CSS property name /// @param $map - A Sass map of viewport unit and size value pairs /// @requires function linear-interpolation /// @requires function map-sort /// @example /// @include poly-fluid-sizing('font-size', (576px: 22px, 768px: 24px, 992px: 34px)); /// @author Jake Wilson <[email protected]> @mixin poly-fluid-sizing($property, $map) { // Get the number of provided breakpoints $length: length(map-keys($map)); // Error if the number of breakpoints is < 2 @if ($length < 2) { @error "poly-fluid-sizing() $map requires at least values" } // Sort the map by viewport width (key) $map: map-sort($map); $keys: map-keys($map); // Minimum size #{$property}: map-get($map, nth($keys,1)); // Interpolated size through breakpoints @for $i from 1 through ($length - 1) { @media (min-width:nth($keys,$i)) { $value1: map-get($map, nth($keys,$i)); $value2: map-get($map, nth($keys,($i + 1))); // If values are not equal, perform linear interpolation @if ($value1 != $value2) { #{$property}: linear-interpolation((nth($keys,$i): $value1, nth($keys,($i+1)): $value2)); } @else { #{$property}: $value1; } } } // Maxmimum size @media (min-width:nth($keys,$length)) { #{$property}: map-get($map, nth($keys,$length)); } }
Этот миксин Sass требует нескольких функций Sass в следующих описаниях Github:
- линейная интерполяция
- карта-сортировка
- сортировка списком
- список-удалить
poly-fluid-sizing()
будет выполнять линейную интерполяцию для каждой пары ширины области просмотра и устанавливать минимальный и максимальный размер. Вы можете импортировать это в любой проект Sass и легко использовать, не зная математики, стоящей за этим. Вот окончательный вариант CodePen, в котором используется этот метод.
Определение размера Poly Fluid с использованием линейных уравнений, единиц просмотра и calc() user="jakobud"] См. Pen Определение размера Poly Fluid с использованием линейных уравнений, единиц просмотра и calc()"] Определение размера Poly Fluid с использованием линейных уравнений, единиц измерения окна просмотра и calc() Джейк Уилсон (@jakobud) на CodePen.
Несколько заметок
- Очевидно, что этот метод применяется не только к
font-size
но и к любому свойству единицы измерения/длины (margin
,padding
и т. д.). Вы передаете желаемое имя свойства в миксин в виде строки. - Карта Sass, состоящая из пар ширины области просмотра и размера, может быть передана в любом порядке в
poly-fluid-sizing()
. Он автоматически сортирует карту по ширине области просмотра от самой низкой до самой высокой . Таким образом, вы можете передать такую карту, и это сработает просто отлично:
$map: (576px: 22px, 320px: 18px, 992px: 34px, 768px: 24px); @include poly-fluid-sizing('font-size', $map);
- Ограничение для этого метода заключается в том, что вы не можете передавать смешанные единицы в миксин. Например,
3em
при ширине576px
. Sass просто не будет знать, что там математически делать.
Заключение
Это лучшее, что мы можем сделать? Является ли Poly Fluid Sizing Святым Граалем для определения размера жидкости в CSS? Может быть. CSS в настоящее время поддерживает нелинейную анимацию и функции синхронизации перехода, поэтому, возможно, есть шанс, что calc()
также когда-нибудь будет поддерживать это. Если это произойдет, нелинейная полиномиальная регрессия, возможно, стоит рассмотреть еще раз. А может и нет… Линейное масштабирование в любом случае может быть лучше.
Я начал исследовать эту идею в начале 2017 года и в итоге разработал вышеуказанное решение. С тех пор я видел, как несколько разработчиков придумали похожие идеи и разные части этой головоломки. Я подумал, что пришло время поделиться своим методом и тем, как я к нему пришел. Единицы области просмотра. Рассчитать(). Сасс. Контрольные точки. Ни одна из этих вещей не нова. Это все функции браузера, которые существуют годами (с разной степенью поддержки). Я использовал их только вместе способом, который еще не был полностью исследован. Никогда не бойтесь смотреть на инструменты, которые вы используете каждый день, и думать нестандартно о том, как вы можете использовать их лучше и развивать свои навыки.