Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

19

Від автора: сьогодні ми поговоримо про те, як відкалібрувати в CSS розмір елементів. При роботі з креативними дизайнерами над дизайнами веб-сторінок доволі часто доводиться одержувати кілька начерків або макетів Sketch або Photoshop, по одному на кожен брейкпоинт.

В такому дизайні елементи (наприклад, заголовок h1) найімовірніше будуть різних розмірів у кожному брейкпоинте. Наприклад:

При малому макеті h1 22px

На макеті середнього розміру 24px

При великій компонуванні може бути 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-sizes не може бути таким? Для згладжування цього переходу можна використовувати CSS-перехід:

h1 {
font-size: 22px;
transition: font-size 0.2 s;
}

Тепер це не так сильно дратує, але стрибок явно все ще там. Що ще ми можемо з цим зробити?

Допоможуть вьюпорт одиниці?

Одиниці вьюпорт — це крок у правильному напрямку. Вони дозволяють вашому тексту гнучко змінювати розміри макетів. І підтримка браузерів в наші дні велика.

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

Життєздатність одиниць вьюпорт дуже сильно залежить від оригінальних креативних проектів веб-сторінки. Було б здорово просто налаштувати font-size, використовуючи vw і зробити так:

h1 {
font-size: 2vw;
}

Але це працює, тільки якщо творчі макети враховують це. Думаєте, це дизайнер вибрав розмір тексту, який склав рівно 2% від ширини кожної з його макетів? Звичайно немає. Давайте з’ясуємо, яке vw значення повинно бути для кожного брейкпоинта:

22px size @ 576px wide = 22/576 = 3.82% of the width
24px size @ 768px wide = 24/768 = 3.13% of the width
34px size @ 992px wide = 34/992 = 3.43% of the width

Вони схожі, але не однакові. Тому все одно доведеться використовувати медіа-запити для переходу між розмірами тексту, і все одно будуть скачки. Розглянемо цей дивний побічний ефект:

@ 767px, 3,82% ширини вікна перегляду — 29 пікселів. Варто зробити розмір браузера на 1 піксель ширше, і font-size раптове падає до 24 пікселів. Це дивно.

Отже, як ми вирішуємо цю проблему?

Статична лінійна регресія?

Стоп. Що? Так, це стаття про CSS, але деякі основні математичні рішення можуть значно допомогти нам елегантно вирішити цю проблему. По-перше, давайте намалюємо наші дозволу і відповідні розміри тексту на графіку:

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

Тут можна побачити графік розкиду заданих дизайнером розмірів тексту при певній ширині вьюпорт. Бачите цю лінію? Це називається трендлайн. Це свого роду спосіб знайти интерполированное значення font-size для будь-якої ширини вікна перегляду на основі даних, які ми надали.

Трендлайн — ключ до всього

Якщо б ви могли встановити font-size позицію відповідно до трендлайном, то у вас був би h1, який плавно масштабується у всіх дозволах, наближених до того, що припускав дизайнер. Давайте згадаємо математику. Пряма визначається цим рівнянням:

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

m = нахил

b = y-перехоплення.

x = поточна ширина вікна

y = результат font-size

Існують кілька методів визначення нахилу і у-перехоплення. Але загальним методом є встановлення найменших квадратів:

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

Після виконання цих розрахунків у вас є рівняння трендлайна.

Як це використовується в CSS?

Згоден, це досить важко. Як я дійсно використовую цей матеріал у веб-розробці під front-end? Відповідь — CSS calc()! Повторю, це досить нова технологія CSS, яка дуже добре підтримується.

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

Ви можете використовувати трендлайн-рівняння наступним чином:

h1 {
font-size: calc({slope}*100vw + {y-intercept}px);
}

Як тільки ви знайдете свій нахил і y-перехоплення, просто підключіть їх і Viola!

Примітка. Вам потрібно помножити нахил на 100, оскільки ви використовуєте його як vw одиницю, яка становить 1/100-й ширини вьюпорт.

Чи може це бути автоматизовано?

Я помістив метод найменших квадратів в зручну функцію SASS:

/// leastSquaresFit
/// Calculate the least square fit of linear regression 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: leastSquaresFit((576: 24, 768: 24, 992: 34));
/// @author Jake Wilson
@function leastSquaresFit($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}px);
}

Це дійсно працює? Відкрийте цей CodePen змініть розмір вікна браузера. Воно працює! Розміри шрифту досить близькі до того, що запитував дизайнер, і вони плавно масштабуються з допомогою макета.

Тепер, за загальним визнанням, це не ідеально. Значення близькі до оригінального дизайну, але не так, як хотілося б. Це пов’язано з тим, що лінійний трендлайн є наближенням конкретних розмірів шрифту при певній ширині вьюпортов. Це наслідок лінійної регресії. В результатах завжди є деяка помилка. Це компроміс між простотою і точністю.

Крім того, чим більш різноманітні розміри вашого тексту, тим більше помилок буде в трендлайне. Ми можемо зробити ще краще?

Поліноміальні найменші квадрати

Щоб отримати більш точний трендлайн, потрібно переглянути більш складні теми, наприклад, трендлайн поліноміальної регресії, який може виглядати приблизно так:

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

Тепер це більше схоже на правду! Набагато точніше нашій прямій лінії. Рівняння базової поліноміальної регресії виглядає наступним чином:

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

Чим точніше ви хочете свою криву, тим складніше виходить рівняння. На жаль, це не можна зробити в CSS. calc() просто не вміє вирішувати такий тип передовий математики. Зокрема, не можна обчислити показники:

font-size: calc(3vw * 3vw); /* This doesn’t work in CSS */

Тому, поки calc() підтримує тип нелінійної математики, ми дотримуємося тільки лінійних рівнянь. Чи є щось ще, що допомогло б нам зробити все краще?

Брейкпоинты + множинні лінійні рівняння

Що, якби ми обчислювали тільки пряму лінію між кожною парою брейкпоинтов? Щось на зразок цього:

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

У цьому прикладі ми обчислюємо пряму лінію між 22px і, 24px, а потім між 24px і 34px. SASS буде виглядати так:

// SCSS
h1 {
@media (min-width:576px) {
font-size: calc(???);
}
@media (min-width:768px) {
font-size: calc(???);
}
}

Ми могли б використовувати метод найменших квадратів для calc() значень, але оскільки це всього лише пряма лінія між двома точками, що математика може бути значно простіше. Пам’ятайте рівняння прямої?

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

Оскільки зараз ми говоримо про двох пунктах, знаходження нахилу (m) і y-перехоплення (b) тривіально:

Використання calc(), vw, брейкпоинтов і лінійних рівнянь в CSS Poly Fluid Sizing — калібруючи розміри

Для цього є функція 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
@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.04166667 vw + 16px);
}
}
@media (min-width: 768px) {
h1 {
font-size: calc(4.46428571 vw — 10.28571429 px);
}
}
@media (min-width: 992px) {
h1 {
font-size: 34px;
}
}

Давайте завершимо все це приємним SASS mixin (для ледачих і ефективних!). Я використовую метод: 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
@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 by the map 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 mixin вимагає наступних функцій SASS:

лінійна інтерполяція

карта сортування

список сортування

список видалення

Насамперед, цей метод, очевидно, відноситься не тільки до font-size, але і до будь власності одиниці довжини ( margin, paddingи тощо). Ви передаєте шукане ім’я властивості mixin як рядок.

Потім ви передаєте будь-яку кількість зіставлених пар ширини вьюпорта + розмір у будь-якому порядку в poly-fluid-sizing()mixin. Він автоматично сортує карту у відповідності з шириною вьюпорта від найнижчого до найвищого . Таким чином, можна передати абсолютно божевільну карту, подібну до цієї, і все чудово вийде:

h1 {
$map: (576px: 22px, 320px: 18px, 992px: 34px, 768px: 24px);
@include poly-fluid-sizing(‘font-size’, $map);
}

Потім він виконає лінійну інтерполяцію для кожної пари ширини вьюпорта, а потім згенерує CSS. Ви можете імпортувати будь-який проект SASS і легко використовувати. Ось остаточний CodePen цього методу:

Єдиний недолік на даний момент, який я бачу, полягає в тому, що не можна ввести змішані одиниці в mixin, наприклад 3em @ 576px ширина. SASS просто не знає, що з цим робити. Я збираюся продовжувати шукати спосіб впоратися з тим, що не входить базове em значення.

Висновок

Це найкраще, що ми можемо зробити? Є Poly Fluid калібруванням розмірів Святого Грааля в CSS? Може бути. Якби нелінійна, поліноміальна регресія використовувалася calc(), було б набагато краще. Але, можливо… все це не залежить від того, хочете ви лінійного масштабування чи ні.

CSS в даний час підтримує нелінійну анімацію та перехідні функції синхронізації, так що, можливо, є шанс, що calc() буде це коли-небудь підтримувати.

Я почав вивчати цю ідею на початку 2017 року і в підсумку розробив вищезгадане рішення. З тих пір я бачив, як деякі розробники придумували схожі ідеї і різні частини цієї головоломки. Я подумав, що настав час мені поділитися своїм методом і тим, як я до нього прийшов.