Адаптивный (резиновый) дизайн является нормой фронтэнд-разработки уже давно. Однако идея гибкой адаптивной типографики является относительно новой, которую еще предстоит изучить. Вплоть до недавнего времени реализация гибкой типографики сводилась к простому использованию Viewport, возможно, с учетом минимальных и максимальных значений.
В этой статье мы перейдем на следующий уровень, рассмотрев создание масштабируемой типографики для нескольких брейкопойнтов и заданных размеров шрифтов, используя хорошо поддерживаемые функции браузера и некоторую базовую математику.
Самое приятное то, что вы можете автоматизировать все это с помощью Sass.
При работе над дизайном сайтов дизайнеры нередко делают несколько артбордов в Скетче или Фотошопе для каждого размера страницы. В это случае для каждого элемента получается свое значение. Например заголовок H1:
Для реализации используются media queries:
Это неплохое решение, но недостатком является то, что при изменении размера окна, размер шрифта меняется резко.
Хорошо, если бы была возможность сделать этот переход плавным. Можно использовать transition:
Переход не так бросается в глаза, но все еще отчетливо виден. Что еще можно с этим сделать?
Viewport позволяет плавно изменять размер шрифта. К тому же этот способ хорошо поддерживается браузерами.
Но жизнеспособность Viewport слишком зависит от изначального дизайна страницы. Было бы отлично просто назначать font-size, используя vw:
Но это работает, только если артборды от дизайнера учитывают это. Выбрал ли дизайнер размер текста, который составляет ровно 2% ширины каждой из его артбордов? Конечно нет.
Давайте вычислим, какое значение vw необходимо для каждого случая:
Они близки, но не одинаковы. Таким образом, все равно придется использовать media queries для смены размеров текста, и все равно будут переходы. И нельзя забывать о странном побочном эффекте:
Так как же решить проблему?
Что? Да, это статья о CSS, но использование основ математики поможет нам найти элегантное решение. Во-первых, давайте построим графики для соответствующих размеров текста:
Здесь наглядно виден разброс изначально заданных размеров шрифта от реальной ширины экрана. Линия на графике называется трендлайн, которая помогает найти интерполированное значение размера шрифта для любой ширины экрана на основе представленных данных.
Если бы вы могли установить свой размер шрифта в соответствии с этой линией тренда, у вас был бы заголовок H1, который плавно масштабировался бы при любом размере экрана. д
Давайте посмотрим на математику. Прямая определяется простым уравнением:
Существуют несколько методов определения m и b. Распространенным методом является подбор по методу наименьших квадратов:
Ответом будет calc()! Это довольно новая CSS технология, но уже хорошо поддерживаемая браузерами:
Уравнение выглядит так:
Метод подгонки наименьших квадратов можно перевести в простую в функцию Sass:
Это действительно работает? Откройте пример на CodePen и измените размер окна браузера. Работает! Размеры шрифта довольно близки к тому, что требовалось для первоначального дизайна, и они плавно масштабируются вместе с макетом.
Хотя решение простое, но оно еще не идеально. Значения близки к данным исходного дизайна, но они не совсем совпадают. Это связано с тем, что линейное уравнение отображает приблизительное значение размеров шрифта. Можно ли это улучшить?
Чтобы получить более точную линию, вам нужно взглянуть на более сложные решения, такие как линия полиномиальной регрессии, которая может выглядеть примерно так:
Теперь это больше похоже на то, что нам нужно! Гораздо точнее нашей прямой линии.
Уравнение основной полиномиальной регрессии выглядит так:
Чем точнее ваша кривая, тем сложнее получается уравнение. К сожалению, вы не можете сделать это в CSS. Calc() просто не может делать подобные вычисления. В частности, вы не можете рассчитать показатели:
Итак, до тех пор, пока calc () не поддержит этот тип нелинейной математики, мы будем зависеть только от линейных уравнений. Можем ли мы что-нибудь еще улучшить?
Что, если бы мы используем прямые линии между каждой парой брейкопойнтов? Что-то вроде этого:
В этом случае мы вычислили прямую линию между 22px и 24px, а затем еще между 24px и 34px.
Sass будет выглядеть так:
Мы могли бы использовать метод подбора наименьших квадратов для этих значений calc (), но поскольку это просто прямая линия между двумя точками, математика может быть значительно упрощена.
Помните уравнение для прямой линии?
Поскольку сейчас речь идет всего лишь о двух точках: m и b, то решение более простое:
Sass для этого:
Генерируемый CSS:
В итоге получается Sass mixin:
Для реализации потребуется несколько Sass функций:
Poly-fluid-sizing () будет выполнять линейную интерполяцию на каждой паре ширины экрана и устанавливать минимальный и максимальный размер. Вы можете импортировать это в любой проект Sass и легко использовать его, не применяя никаких знаний математики.
Вот окончательный пример на CodePen, который использует описанный метод.
Это лучшее, что мы можем сделать для гибкой адаптивной типографики? Возможно. CSS в настоящее время поддерживает функции нелинейной анимации и transition-timing, поэтому, возможно, когда-нибудь calc () также будет это поддерживать.
Если это произойдет, возможно стоит взглянуть на нелинейную, полиномиальную регрессию еще раз.
Но возможно и нет… Линейное масштабирование может быть лучше.
В этой статье мы перейдем на следующий уровень, рассмотрев создание масштабируемой типографики для нескольких брейкопойнтов и заданных размеров шрифтов, используя хорошо поддерживаемые функции браузера и некоторую базовую математику.
Самое приятное то, что вы можете автоматизировать все это с помощью Sass.
При работе над дизайном сайтов дизайнеры нередко делают несколько артбордов в Скетче или Фотошопе для каждого размера страницы. В это случае для каждого элемента получается свое значение. Например заголовок H1:
- H1 для маленького размера равен 22 пикселя
- H1 для среднего размера равен 24 пикселя
- H1 для большого размера равен 34 пикселя
Для реализации используются media queries:
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;
}
}
Это неплохое решение, но недостатком является то, что при изменении размера окна, размер шрифта меняется резко.
Хорошо, если бы была возможность сделать этот переход плавным. Можно использовать transition:
h1 {
font-size: 22px;
transition: font-size 0.2s;
}
Переход не так бросается в глаза, но все еще отчетливо виден. Что еще можно с этим сделать?
Viewport
Viewport позволяет плавно изменять размер шрифта. К тому же этот способ хорошо поддерживается браузерами.
Но жизнеспособность Viewport слишком зависит от изначального дизайна страницы. Было бы отлично просто назначать font-size, используя vw:
h1 {
font-size: 2vw;
}
Но это работает, только если артборды от дизайнера учитывают это. Выбрал ли дизайнер размер текста, который составляет ровно 2% ширины каждой из его артбордов? Конечно нет.
Давайте вычислим, какое значение vw необходимо для каждого случая:
- Размер 22px @ ширина 576px = 22/576*100 = 3.82vw
- Размер 24px @ ширина 768px = 24/768*100 = 3.13vw
- Размер 34px @ ширина 992px = 34/992*100 = 3.43vw
Они близки, но не одинаковы. Таким образом, все равно придется использовать media queries для смены размеров текста, и все равно будут переходы. И нельзя забывать о странном побочном эффекте:
Так как же решить проблему?
Статистическая линейная регрессия
Что? Да, это статья о CSS, но использование основ математики поможет нам найти элегантное решение. Во-первых, давайте построим графики для соответствующих размеров текста:
Здесь наглядно виден разброс изначально заданных размеров шрифта от реальной ширины экрана. Линия на графике называется трендлайн, которая помогает найти интерполированное значение размера шрифта для любой ширины экрана на основе представленных данных.
Если бы вы могли установить свой размер шрифта в соответствии с этой линией тренда, у вас был бы заголовок H1, который плавно масштабировался бы при любом размере экрана. д
Давайте посмотрим на математику. Прямая определяется простым уравнением:
Существуют несколько методов определения m и b. Распространенным методом является подбор по методу наименьших квадратов:
Как использовать все это в CSS?
Ответом будет calc()! Это довольно новая CSS технология, но уже хорошо поддерживаемая браузерами:
Уравнение выглядит так:
h1 {
font-size: calc({slope}*100vw + {y-intercept}px);
}
Можно ли это автоматизировать?
Метод подгонки наименьших квадратов можно перевести в простую в функцию 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 <jake.e.wilson@gmail.com>
@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 и измените размер окна браузера. Работает! Размеры шрифта довольно близки к тому, что требовалось для первоначального дизайна, и они плавно масштабируются вместе с макетом.
Хотя решение простое, но оно еще не идеально. Значения близки к данным исходного дизайна, но они не совсем совпадают. Это связано с тем, что линейное уравнение отображает приблизительное значение размеров шрифта. Можно ли это улучшить?
Полиномиальная регрессия
Чтобы получить более точную линию, вам нужно взглянуть на более сложные решения, такие как линия полиномиальной регрессии, которая может выглядеть примерно так:
Теперь это больше похоже на то, что нам нужно! Гораздо точнее нашей прямой линии.
Уравнение основной полиномиальной регрессии выглядит так:
Чем точнее ваша кривая, тем сложнее получается уравнение. К сожалению, вы не можете сделать это в CSS. Calc() просто не может делать подобные вычисления. В частности, вы не можете рассчитать показатели:
font-size: calc(3vw * 3vw);
Итак, до тех пор, пока calc () не поддержит этот тип нелинейной математики, мы будем зависеть только от линейных уравнений. Можем ли мы что-нибудь еще улучшить?
Брейкпойнты и множественные линейные уравнения
Что, если бы мы используем прямые линии между каждой парой брейкопойнтов? Что-то вроде этого:
В этом случае мы вычислили прямую линию между 22px и 24px, а затем еще между 24px и 34px.
Sass будет выглядеть так:
h1 {
@media (min-width:576px) {
font-size: calc(???);
}
@media (min-width:768px) {
font-size: calc(???);
}
}
Мы могли бы использовать метод подбора наименьших квадратов для этих значений calc (), но поскольку это просто прямая линия между двумя точками, математика может быть значительно упрощена.
Помните уравнение для прямой линии?
Поскольку сейчас речь идет всего лишь о двух точках: m и 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 <jake.e.wilson@gmail.com>
@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});
}
Now, just use the linear interpolation function on multiple breakpoints in your Sass. Also, lets throw in some min and max 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;
}
}
Финальное решение
В итоге получается Sass mixin:
/// 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 <jake.e.wilson@gmail.com>
@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 функций:
Poly-fluid-sizing () будет выполнять линейную интерполяцию на каждой паре ширины экрана и устанавливать минимальный и максимальный размер. Вы можете импортировать это в любой проект Sass и легко использовать его, не применяя никаких знаний математики.
Вот окончательный пример на CodePen, который использует описанный метод.
Заключение
Это лучшее, что мы можем сделать для гибкой адаптивной типографики? Возможно. CSS в настоящее время поддерживает функции нелинейной анимации и transition-timing, поэтому, возможно, когда-нибудь calc () также будет это поддерживать.
Если это произойдет, возможно стоит взглянуть на нелинейную, полиномиальную регрессию еще раз.
Но возможно и нет… Линейное масштабирование может быть лучше.
Поделиться с друзьями
Комментарии (10)
frees2
15.05.2017 18:24+1font-size: 2vw;
Если вы уменьшаете размеры ячейки для меньшего экрана шрифт надо увеличивать, а не уменьшать.
Odrin
15.05.2017 18:57+1Мы в своих проектах используем это решение для плавного изменения размеров: Математика CSS-шлюзов
Denai
15.05.2017 19:34+2Какая разница насколько плавно меняется шрифт, если при повороте экрана планшета видимая надпись всё равно уедет за его пределы?
volk0ff
16.05.2017 12:57H1 для маленького размера равен 34 пикселя
тут не очепятка? Может все-таки для большого?
Спасибо
AngReload
16.05.2017 18:57Но для прохождения трех точек нужна квадратичная, а не кубическая функция.
y == a + b * x + c * x * x
noodles
17.05.2017 11:34+2Может кому пригодится, написал себе такой сниппет, уже как более полугода использую (как прочитал эту статью http://fvsch.com/code/css-locks/ ).
/*=========================АДАПТИВНЫЙ ШРИФТ=========================*/ /*миксин возвращает адаптивный шрифт между указанными ширинами экранов, за пределами этих ширин шрифт оставляет неизменными свои указанные крайние значения, т.е. миксин на вход получает следующие параметры: - $bigFont - шрифт которой мы хотим получить при ширине экрана $bigView, - $smallFont - шрифт которой мы хотим получить при ширине экрана $smallView, соответственно в промежутке между указанными ширинами экранов - шрифт будет изменятся линейно*/ @mixin responsiveFont($bigFont, $smallFont, $bigView, $smallView){ //http://fvsch.com/code/css-locks/ // y = m * x + b - уравнение прямой, где: // y - вертикальная ось с нужными нам шрифтами, // x - горизонтальная ось с шириной экрана (вьюпорта), // b - промежуток между пересечением графика (в данном случае это // функция прямой линии) с вертикальной осью x, // m - наклон прямой (степерь прироста координаты y при // каждом увеличении вьюпорта на 1px); $m: ($bigFont - $smallFont) / ($bigView - $smallView); $b: $smallFont - ($m * $smallView); @media only screen and (min-width: $smallView){ font-size: calc(#{$m} * 100vw + #{$b}); } @media only screen and (min-width: $bigView){ font-size: $bigFont; } }
статью просмотрел по диагонали, подозреваю речь о том же..))
776166
Зачем менять размер шрифта плавно, если для этого нет задач? :)
bano-notit
Ни всегда… Есть задача, когда ты делаешь что-то типа своего оконного менагера (а такое у каких-нибудь полубухгалтерских приложеньках нужно). Ну конечно задача очень узкая, но тогда ты уже начинаешь либо использовать
@media
и iframe, что, вообще-то, как-то не современно. Либо начинаешь делать одно приложение, которое внутри себя делает вот такие вот окошки и делаешь свой велосипед для@media
на js...