Математика CSS-шлюзів


CSS-шлюзом (CSS-lock) називається методика адаптивного веб-дизайну, що дозволяє не перестрибувати від одного значення до іншого, а переходити плавно, в залежності від поточного розміру області перегляду (viewport). Ідею і одну з реалізацій запропонував Тім Браун у статті Flexible typography with CSS locks. Коли я намагався розібратися з його реалізацією і створити свої варіанти, мені насилу вдавалося зрозуміти, що саме відбувається. Я виконав багато обчислень і подумав, що корисно буде пояснити іншим всю цю математику.

У статті я опишу саму методику, її обмеження і лежить в її основі математику. Не хвилюйтеся: там в основному одні додавання і віднімання. До того ж я постарався все розбити на етапи і прикрасив їх графіками.

Що таке CSS-шлюз?
Залежність від розміру області перегляду
В моїх останніх проектах використовувався банер на всю ширину з заголовком і одні лише «десктопні» шаблони з великими шрифтами. Я вирішив, що мені потрібні маленькі шрифти для невеликих екранів і щось для екранів проміжних розмірів. Так чому б не поставити розмір шрифтів у залежність від ширини області перегляду?

Раніше це робили приблизно так:

h1 { font-size: 4vw; /* Бум! Готове. */ }

У цього підходу є дві проблеми:

  1. На дуже маленьких екранах текст стає крихітним (12,8 пікселів у висоту при ширині екрану 320 пікселів), на великих — величезний (64 при 1600);
  2. Не враховуються користувальницькі налаштування розміру шрифту.
CSS-шлюзи дозволяють позбутися від першої проблеми. Чудові CSS-шлюзи також постараються враховувати і користувальницькі налаштування.

Ідея CSS-шлюзу
CSS-шлюз — це особливий вид обчислення CSS-значення, при якому:

  • є мінімальне і максимальне значення,
  • є дві контрольні точки (breakpoint) (зазвичай залежать від ширини області перегляду),
  • між цими точками значення змінюється лінійно від мінімуму до максимуму.


«При ширині менше 320 пікселів будемо використовувати шрифти 20px, понад 960 пікселів — 40px, а між 320 і 960 — від 20px до 40px».

На стороні CSS це може виглядати так:

h1 { font-size: 1.25 rem; }

@media (min-width: 320px) {
h1 { font-size: /* чарівне значення від 1.25 rem до 2.5 rem */; }
}

@media (min-width: 960px) {
h1 { font-size: 2.5 rem; }
}

Перше завдання — реалізувати чарівне значення. Трохи зіпсую вам задоволення і відразу скажу, що це виглядає так:

h1 {
font-size: calc(1.25 rem + viewport_relative_value);
}

Тут
viewport_relative_value
може бути одиночним значенням (наприклад,
3vw
) або представляти собою більш складне обчислення (на основі одиниці виміру області перегляду
vw
або якоїсь іншої одиниці).

Обмеження
Оскільки CSS-шлюзи зав'язані на одиниці виміру області перегляду, шлюзи мають ряд важливих обмежень. Вони можуть приймати тільки числові значення, використовувати
calc()
та приймати значення в пікселях.

Чому так? Тому що одиниці вимірювання області перегляду (
vw
,
vh
,
vmin
та
vmax
) завжди визначаються в пікселях. Наприклад, якщо ширина області перегляду — 768 пікселів, то
1vw
визначається 7,68 пікселя.

(У статті Тіма є помилка: він пише, що обчислення зразок
100vw - 30em
дають значення
m
. Це не так. Браузер вважає
100vw
в пікселях і віднімає з нього значення
30em
для цього елемента і властивості.)

Деякі приклади того, що не працює:

  • CSS-шлюз для властивості
    opacity
    , тому що
    opacity: calc(.5+1px)
    є помилкою;
  • CSS-шлюз для більшості функцій
    transform
    (наприклад,
    rotate
    : шлюз не може виконувати обертання на підставі значення в пікселях).
Отже, у нас є растрові обмеження. Можливо, знайдуться сміливці і вирахують всі властивості і методики, які може використовуватися в CSS-шлюзах.

Для початку візьмемо властивості
font-size
та
line-height
і подивимося, як можна створювати для них CSS-шлюзи з контрольними точками на основі пікселів або em.

CSS-шлюзи з піксельними контрольними точками
Демки
Далі ми розглянемо, як одержати CSS-код для кожного з цих прикладів.

Розмір шрифту як лінійна функція
Нам потрібно, щоб font-size пропорційно збільшувався з 20px при ширині області 320px до 40px при ширині 960px. Відобразимо це на графіку:



Червона лінія — це графік лінійної функції. Можна записати її як
y = mx + b
:

  • y
    — розмір шрифту (вертикальна вісь),
  • x
    — ширина області перегляду в пікселях (горизонтальна вісь),
  • m
    наклон (slope) функції (скільки пікселів ми додаємо до розміру шрифту при збільшенні ширини області перегляду на 1 піксель),
  • b
    — розмір шрифту до того, як ми починаємо збільшувати розмір області перегляду.
Нам потрібно обчислити
m
та
b
. У рівнянні вони є константами.

Спочатку розберемося з
m
. Для цього потрібні тільки координати
(x,y)
. Це схоже на обчислення швидкості (дистанція, пройдена за одиницю часу), але в даному випадку ми обчислюємо розмір шрифту залежно від ширини області перегляду:

m = font_size_increase / viewport_increase
m = (y2 - y1) / (x2 - x1)
m = (40 - 20) / (960 - 320)
m = 20 / 640
m = 0.03125

Інша форма:

Загальне збільшення font-size — 20 пікселів (
40 - 20
).
Загальне зменшення області перегляду — 640 пікселів (
960 - 320
).
Якщо ширина області зросте на 1 піксель, наскільки збільшиться розмір font-size?

20 / 640 = 0.03125 px.


Тепер обчислимо
b
.

y = mx + b
b = y - mx
b = y - 0.03125 x

Оскільки наша функція перевіряється з допомогою обох цих точок, ми можемо використовувати координати
(x,y)
. Візьмемо першу:

b = y1 - 0.03125 × x1
b = 20 - 0.03125 × 320
b = 10

До речі, обчислити ці 10 пікселів можна було, просто подивившись на графік. Але ж він не завжди у нас є :-)

Тепер наша функція виглядає так:

y = 0.03125 x + 10

Перетворення в CSS
y
— розмір font-size, і, якщо ми хочемо виконати базові операції в CSS, нам потрібен
calc()
.

font-size: calc( 0.03125 x + 10px );

Звичайно, це псевдоСЅЅ, бо
x
не є валідним синтаксисом. Але в нашій лінійної функції
x
представляє ширину області перегляду, яку в CSS можна виразити як
100vw
.

font-size: calc( 0.03125 * 100vw + 10px );

Ось тепер вийшов працює CSS. Якщо треба висловити більш коротко, виконаємо множення. Оскільки
0.03125 × 100 = 3.125
:

font-size: calc( 3.125 vw + 10px );

Тепер обмежимо ширину області перегляду 320 та 960 пікселями. Додамо кілька media-запитів:

h1 { font-size: 20px; }

@media (min-width: 320px) {
h1 { font-size: calc( 3.125 vw + 10px ); }
}

@media (min-width: 960px) {
h1 { font-size: 40px; }
}

Тепер наш графік виглядає як потрібно:



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

Застосування налаштувань
Практично кожен браузер дозволяє користувачам задавати розмір тексту за замовчуванням. Найчастіше воно спочатку дорівнює 16 пікселям, але іноді його змінюють (зазвичай збільшують).
Я хочу вставити користувача настройки в нашу формулу і для цього зверну увагу на значення
rem
. Щодо
m
та процентних значень застосовується той же принцип.

Спочатку перевіримо, що базового (root) font-size не присвоєно абсолютне значення. Наприклад, якщо ви використовуєте CSS з Bootstrap 3, там зустрічається чимало такого:

html {
font-size: 10px;
}

Ніколи так не робіть! (На щастя, в Bootstrap 4 це виправили.) Якщо вам дійсно потрібно змінити значення базового em (
1rem
), використовуйте:

/*
* Змінює значення rem з дотриманням пропорційності.
* При розмірі за замовчуванням font-size 16 пікселів:
* • 62.5% -> 1rem = 10px, .1rem = 1px
* • 125% -> 1rem = 20px, .05rem = 1px
*/
html {
font-size: 62.5%;
}

Тим не менш залишимо в спокої базовий font-size, нехай він буде за замовчуванням дорівнює 16 пикселів. Давайте подивимося, що станеться, якщо в нашому font-size-шлюзі замінити піксельні значення
rem
-значеннями:

/*
* З користувацькими налаштуваннями за замовчуванням:
* • 0.625 rem = 10px
* • 1.25 rem = 20px
* • 2.5 rem = 40px
*/
h1 { font-size: 1.25 rem; }

@media (min-width: 320px) {
h1 { font-size: calc( 3.125 vw + .625rem ); }
}

@media (min-width: 960px) {
h1 { font-size: 2.5 rem; }
}

Якщо запустити код з браузерних налаштуваннями за замовчуванням, то він буде вести себе, як попередній код, що використав пікселі. Чудово!

Але оскільки ми зробили це заради підтримки вносяться користувачами зміни, потрібно перевірити, як все працює. Припустимо, користувач задав розмір шрифту 24 пікселя замість 16 (на 50 % більше). Як поведе себе код?



Синя лінія: font-size за замовчуванням дорівнює 16 пікселях.
Червона лінія: font-size за замовчуванням дорівнює 24 пикселів.

При збільшенні області перегляд до 320 пікселів шрифт стає менше (з 30 пікселів зменшується до 25), а при досягненні другої контрольної точки збільшується стрибкоподібно (з 45 до 60 пікселів). Ой.

Виправити це допоможе той же настроюється користувачем базове значення (baseline) для всіх трьох розмірів. Наприклад, виберемо
1.25 rem
:

h1 { font-size: 1.25 rem; }

@media (min-width: 320px) {
h1 { font-size: calc( 1.25 rem + 3.125 vw - 10px ); }
}

@media (min-width: 960px) {
h1 { font-size: calc( 1.25 rem + 20px ); }
}

Зверніть увагу на
3.125 vw - 10px
. Це наша стара лінійна функція (у вигляді
mx + b
), але з іншим значенням
b
. Назвемо його
b'
. В даному випадку ми знаємо, що базове значення дорівнює 20 пікселям, тому можемо отримати значення
b'
простим відніманням:

b' = b - baseline_value
b' = 10 - 20
b' = 10

Інший спосіб — вибирати базове значення з самого початку, а потім шукати лінійну функцію, що описує збільшення font-size (назвемо його
y'
, щоб не плутати зі значенням самого font-size
y
).

x1 = 320
x2 = 960

y'1 = 0
y'2 = 20

m = (y'2 - y'1) / (x2 - x1)
m = (20 - 0) / (960 - 320)
m = 20 / 640
m = 0.03125

b' = y' - mx
b' = y'1 - 0.03125 × x1
b' = 0 - 0.03125 × 320
b' = -10

Отримали функцію
y' = 0.03125 x - 10
, яка виглядає так:



З базовим значенням
rem
і додатковими значеннями в
vw
та/або
px
ми нарешті можемо створити повноцінний працює шлюз для font-size. Коли користувач змінює розмір шрифта, система підлаштовується під нього і не ламається.



Пурпурна лінія: ступінь збільшення font-size.
Синя лінія: font-size за замовчуванням дорівнює 16 пікселях.
Червона лінія: font-size за замовчуванням дорівнює 24 пикселів.

Звичайно, це не зовсім те, що просив користувач: він хотів збільшити шрифт на 50%, а ми збільшили його на 50% в маленьких областях перегляду і на 25% — у великих. Але це хороший компроміс.

Створення шлюзу для висоти рядка
В даному випадку у нас буде такий сценарій: «Я хочу параграфи з висотою рядка 140% при перегляді шириною 320 пікселів і 180% — при 960».

Оскільки ми будемо працювати з базовим значенням плюс динамічно змінним значенням, вираженим в пікселях, нам потрібно знати, скільки пікселів складають коефіцієнти 1,4 і 1,8. Тобто потрібно обчислити
font-size
для наших параграфів. Припустимо, базовий розмір шрифту рівний 16 пикселів. Отримуємо:

  • 16 * 1.4 = 22.4
    пікселя при нижньому розмір області перегляду (
    320 px
    )
  • 16 * 1.8 = 28.8
    пікселя при верхньому розмір області перегляду (
    960 px
    )
В якості базового значення візьмемо
140% = 22.4 px
. Виходить, що загальне збільшення шрифту становить 6,4 пікселя. Скористаємося нашої лінійною формулою:

x1 = 320
x2 = 960

y'1 = 0
y'2 = 6.4

m = (y'2 - y'1) / (x2 - x1)
m = (6.4 - 0) / (960 - 320)
m = 6.4 / 640
m = 0.01

b' = y' - mx
b' = y'1 - 0.01 × x1
b' = 0 - 0.01 × 320
b' = 3.2

y' = 0.01 x - 3.2

Перетворимо в CSS:

line-height: calc( 140% + 1vw - 3.2 px );

Примітка: базове значення потрібно виражати як 140% або
1.4 em
; безрозмірне
1.4
не буде працювати всередині
calc()
.

Потім додаємо media-запити і перевіряємо, щоб оголошення
line-height
використовували одне базове значення (
140%
).

p { line-height: 140%; }

@media (min-width: 320px) {
p { line-height: calc( 140% + 1vw - 3.2 px ); }
}

@media (min-width: 960px) {
p { line-height: calc( 140% + 6.4 px ); }
}

Нагадую: для великої області перегляду можна використовувати просто
180%
, нам потрібна виражена в пікселях частина, яка додається до базового значення. Якщо взяти
180%
, то при базовому розмірі шрифту 16 пікселів все буде нормально, поки користувач не поміняє.

Побудуємо графік і перевіримо роботу коду при різних базових значеннях font-size.



Синя лінія: font-size за замовчуванням дорівнює 16 пікселях.
Червона лінія: font-size за замовчуванням дорівнює 24 пикселів.

Тепер, коли наша формула
line-height
залежить від власного розміру font-size елемента, зміна розміру шрифту призведе до зміни формули. Наприклад, в це демо показаний параграф зі збільшеним текстом, визначеним як:

.big {
font-size: 166%;
}

Це змінює наші контрольні точки:

  • 16 * 1.66 * 1.4 = 37.184
    пікселя при нижньому розмір області перегляду (
    320px
    )
  • 16 * 1.66 * 1.8 = 47.808
    пікселя при верхньому розмір області перегляду (
    960px
    )
Проведемо обчислення і отримаємо оновлену формулу:
y' = 0.0166 x - 5.312
. Потім об'єднаємо в CSS цей і попередній стилі:

p { line-height: 140%; }
.big { font-size: 166%; }

@media (min-width: 320px) {
p { line-height: calc( 140% + 1vw - 3.2 px ); }
.big { line-height: calc( 140% + 1.66 vw - 5.312 px ); }
}

@media (min-width: 960px) {
p { line-height: calc( 140% + 6.4 px ); }
.big { line-height: calc( 140% + 10.624 px ); }
}

Також можна покласти обчислення на CSS. Раз ми використовуємо одні й ті ж контрольні точки і відносні розміри line-heights, як для стандартного параграфа, то нам потрібно лише додати коефіцієнт 1,66:

p { line-height: 140%; }
.big { font-size: 166%; }

@media (min-width: 320px) {
p { line-height: calc( 140% + 1vw - 3.2 px ); }
.big { line-height: calc( 140% + (1vw - 3.2 px) * 1.66 ); }
}
@media (min-width: 960px) {
p { line-height: calc( 140% + 6.4 px ); }
.big { line-height: calc( 140% + 6.4 px * 1.66 ); }
}

Об'єднання шлюзів font-size і line-height
Спробуємо тепер все зібрати воєдино. Сценарій: є адаптується стовпець тексту (fluid column) з H1 і кількома параграфами. Нам потрібно змінити font-size і line-height, використовуючи наступні значення:

Елемент і властивість Значення при 320px Значення при 960px
H1 font-size 24 пікселя 40 пікселів
H1 line-height 133,33% 120%
P font-size 15 пікселів 18 пікселів
P line-height 150% 166,67%
Ви побачите, що з висотою рядка ми робимо дві речі. Є загальне правило: коли текст збільшується, висоту рядка потрібно зменшувати, а коли стовпець стає ширше — збільшувати. Але в нашому сценарії одночасно відбуваються обидві ситуації, що суперечать один одному! Тому потрібно вибрати пріоритети:

  • Для H1 збільшення font-size буде критичніше, ніж збільшення ширини стовпця.
  • Для параграфів збільшення ширини стовпця буде критичніше, ніж невелике збільшення font-size.
Тепер виберемо дві контрольні точки — область перегляду шириною 320 та 960 пікселів. Почнемо з написання шлюзу для font-size:

h1 { font-size: 1.5 rem; }
/* .9375rem = 15px з налаштуваннями за замовчуванням */
p { font-size: .9375rem; }

@media (min-width: 320px) {
h1 { font-size: calc( 1.5 rem + 2.5 vw - 8px ); }
/* .46875vw - 1.5 px одно від 0 до 3px */
p { font-size: calc( .9375rem + .46875vw - 1.5 px ); }
}
@media (min-width: 960px) {
h1 { font-size: calc(1.5 rem + 16px); }
p { font-size: calc( .9375rem + 3px ); }
}

Тут нічого нового, тільки значення помінялися.

Тепер розраховуємо шлюзи для
line-height
. Це буде куди складніше, ніж в минулий раз.

Почнемо з елементу H1. Для
line-height
скористаємося відносними базовим значенням — 120%. Оскільки розмір шрифту елемента у нас змінюємо, то ці 120% дозволяють описати динамічне і лінійне значення, що визначається двома точками:

  • 24 × 1.2 = 28.8 px
    в нижній контрольній точці,
  • 40 × 1.2 = 48px
    у верхній контрольній точці.
У нижній контрольній точці нам потрібно мати значення
line-height
, рівне 133,33%, це близько 32 пікселів.

Знайдемо лінійну функцію, що описує «те, що додається до базового значення 120%». Якщо прибрати ці 120%, отримаємо два модифікованих значення:

  • 24 × (1.3333 - 1.2) = 3.2 px
    в нижній контрольній точці,
  • 40 × (1.2 - 1.2) = 0px
    у верхній контрольній точці.
Повинен вийти від'ємний нахил.

m = (y'2 - y'1) / (x2 - x1)
m = (0 - 3.2) / (960 - 320)
m = -3.2 / 640
m = -0.005

b' = y' - mx
b' = y'1 - (-0.005 × x1)
b' = 3.2 + 0.005 × 320
b' = 4.8

y' = -0.005 x + 4.8

Перетворимо в CSS:

h1 {
line-height: calc( 120% - .5vw + 4.8 px );
}

Подивимося на графік:



Синя лінія: зменшення line-height.
Червона лінія: базове значення line-height (120% font-size заголовка).
Пурпурна лінія: фінальна line-height.

На графіку видно, що результуюча висота рядка (пурпурна лінія) дорівнює базового значення 120% плюс зменшення висоти рядка (синя лінія). Можете самі перевірити обчислення на GraphSketch.com.

Для параграфів ми скористаємося базовим значенням
150%
. Збільшення line-height:

(1.75 - 1.5) × 18 = 4.5 px.



Мій калькулятор каже, що формула така:

y' = 0.00703125 x - 2.25

Щоб побачити повний CSS-код, погляньте на вихідний код демки, в якій об'єднані font-size і line-height. Змінюючи розмір браузерного вікна, ви переконаєтеся, що ефект є, хоч і слабкий.

Також рекомендую потестувати цю демку, змінивши розмір шрифту. Зверніть увагу, що тут співвідношення line-height будуть трохи іншими, але цілком допустимими. Немає нічого поганого в тому, що line-height стає менше базового значення.

Автоматизація обчислень
Готуючи цей розділ, я виконував всі обчислення вручну або за допомогою калькулятора Soulver. Але це досить трудомістке, і висока ймовірність помилок. Щоб виключити людський фактор, добре б впровадити автоматизацію.

Перший спосіб — перенести всі обчислення в CSS. Це варіант формули, використаної в прикладах з font-size, коли детально розбиралися всі значення:

@media (min-width: 320px) and (max-width: 959px) {
h1 {
font-size: calc(
/* y1 */
1.5 rem
/* + m × x */
+ ((40 - 24) / (960 - 320)) * 100vw
/* - m × x1 */ 
- ((40 - 24) / (960 - 320)) * 320px
);
}
}

Але виходить занадто багато букв, можна написати набагато лаконічнішими:

@media (min-width: 320px) and (max-width: 959px) {
h1 {
font-size: calc( 1.5 rem + 16 * (100vw - 320px) / (960 - 320) );
}
}

Так співпало, що цю формулу використовував Тім Браун у статті Flexible typography with CSS locks, правда, з пікселями замість
m
в значенні змінної. Це працює і для комбінованого варіанта з font-size і line-height, але може бути не настільки очевидно, особливо при негативному нахилі.

@media (min-width: 320px) and (max-width: 959px) {
h1 {
font-size: calc( 1.5 rem + 16 * (100vw - 320px) / (960 - 320) );
/* При негативному нахилі потрібно інвертувати контрольні точки */
line-height: calc( 120% + 3.2 * (100vw - 960px) / (320 - 960) );
}
}

Другий спосіб — автоматизувати обчислення з допомогою плагіна Sass або PostCSS mixin.

CSS-шлюзи з em-контрольними точками
Нові демки
Я взяв три перші демки і замість піксельних значень контрольних точок і инкрементирований вставив значення на основі
rem
.

У наступному розділі ми розглянемо роботу специфічного синтаксису в цих демках.

Синтаксис m × 100vw для media-запитів на основі em — не найкраща ідея
Вище ми задіяли синтаксис
m × 100vw
(наприклад, тут
calc(base + 2.5 vw)
). Його не можна використовувати з media-запитами на основі
m
.

Вся справа в контексті media-запитів. Одиниці
m
та
rem
посилаються на одне і те ж: базовий розмір шрифту в User Agent. А він, як ми вже кілька разів бачили, зазвичай дорівнює 16 пікселям, але значення може бути і інше. Чому?

  1. За волею браузера або ОС (в основному в специфічних випадках начебто ТВ-браузерів і читалок).
  2. За волею користувача.
Так що якщо у нас контрольні точки
20em
та
60em
, то вони будуть відповідати реальній CSS-ширині:

  • 320 та 960 пікселів при базовому розмірі шрифту 16 пікселів,
  • 480 і 1440 — при 24 пікселях і т. д.
(Зверніть увагу, що це CSS-пікселі, а не апаратні пікселі. У статті ми не розглядаємо апаратні пікселі, оскільки вони не впливають на наші обчислення.)

Вище наводилися приклади коду на зразок такого:

font-size: calc( 3.125 vw + .625rem );

Якщо в цьому синтаксисі замінити всі контрольні точки з використанням
m
, прийнявши, що в media-запиті 1 em дорівнює 16 пікселях, то вийде:

h1 { font-size: 1.25 rem; }

/* Не робіть так :((( */
@media (min-width: 20em) {
h1 { font-size: calc( 1.25 rem + 3.125 vw - 10px ); }
}

/* Або так. */
@media (min-width: 60em) {
h1 { font-size: calc( 1.25 rem + 20px ); }
}

Це спрацює, якщо ОС, браузер і користувач ніколи не змінюють розмір шрифту. А інакше буде погано:



Синя лінія: font-size за замовчуванням дорівнює 16 пікселях.
Червона лінія: font-size за замовчуванням дорівнює 24 пикселів.

Що тут відбувається? Коли ми змінюємо базовий font-size контрольні точки на основі
m
зміщуються на більш високі піксельні значення. Єдино правильним значенням для конкретних точок буде
3.125 vw - 10px
!

  • При 320 пікселів
    3.125 vw - 10px
    0 пікселям, як і повинно бути.
  • При 480 пікселів
    3.125 vw - 10px
    дорівнює 5 пікселях.
На високих контрольних точках ще гірше:

  • При 960 пікселів
    3.125 vw - 10px
    дорівнює 20 пікселям, як і повинно бути.
  • При 1440 пікселів
    3.125 vw - 10px
    одно 35 пикселів (на 15 більше).
Якщо ви хочете використовувати контрольні на основі
m
, то треба робити інакше.

Знову виконуємо розрахунки
Ця методика продемонстрована в статті Тіма Брауна. Вона передбачає, що більшість обчислень робиться в CSS з використанням двох змінних частин:

  • 100vw
    — ширина області перегляду;
  • нижня контрольна крапка, виражена в
    rem
    .
Скористаємося формулою:

y = m × (x - x1) / (x2 - x1)

Чому саме нею? Давайте розберемо по кроках. Вище ми побачили, що font-size і line-height можна описати лінійною функцією:

y = mx + b


В CSS можна працювати з
x
(
100vw
). Але не можна задати
m
та
b
точні значення в пікселях або
vw
, тому що це константи, виражені в пікселях, і їх можна сплутати з нашими контрольними точками, вираженими в
m
, якщо користувач змінить розмір шрифту.

Спробуємо замінити
m
та
b
іншими відомими значеннями, а саме
(x1,y1)
та
(x2,y2)
.

Знаходимо
b
з допомогою першої пари координат:

b = y - mx
b = y1 - m × x1

Збираємо всі разом:

y = mx + b
y = mx + y1 - m × x1

Ми виключили
b
з формули!

Також вище ми бачили, що насправді нам потрібно було не повне значення
font-size
або
line-height
, а тільки динамічна частина, яку ми додаємо до базового значення. Ми назвали її
y'
і можемо висловити так:

y = y1 + y'
y' = y - y1

Замінимо
y
з допомогою виведеного рівності:

y' = mx + y1 - m × x1 - y1
y' = mx + y1 - m × x1 - y1

Ми можемо позбутися шматків
+ y1
та
y1
!

y' = m × x - m × x1
y' = m × (x - x1)

Тепер можемо замінити
m
вже відомими значеннями:

m = (y2 - y1) / (x2 - x1)

Тоді:

y' = (y2 - y1) / (x2 - x1) × (x - x1)

Також це можна записати так:

y' = max_value_increase × (x - x1) / (x2 - x1)

Перетворення в CSS
Це значення ми можемо використовувати CSS. Повернемося знову до нашого прикладу «від 20 до 40 пікселів»:

@media (min-width: 20em) and (max-width: 60em) {
h1 {
/* УВАГА: це поки що не працює! */
font-size: calc(
1.25 rem /* базове значення */
+ 20px /* різниця між максимальним і базовими значеннями */
* (100vw - 20rem) /* x - x1 */
/ (60rem - 20rem) /* x2 - x1 */
);
}
}

Код поки не працює. Здається, що він міг би працювати, але
calc()
CSS має ряд обмежень, що належать до множенню і діленню.

Почнемо з фрагмента
100vw - 20rem
. Ця частина працює як є і повертає значення в пікселях.

Наприклад, якщо font-size за замовчуванням — 16 пікселів, а ширина області перегляду — 600 пікселів, то результат дорівнює 280 пикселів (
600 - 20 × 15
). Якщо font-size за замовчуванням — 24 пікселя, а ширина області перегляду — 600 пікселів, то результат дорівнює 120 пикселів (
600 - 20 × 24
).



Зверніть увагу, що для вираження наших контрольних точок ми використовуємо одиницю
rem
. Чому не
m
? Тому що в CSS-значення
m
посилається не на базовий font-size, а на власний font-size елемента (в загальному) або на його батьківський font-size (коли використовується властивість
font-size
).

В ідеалі нам потрібна CSS — одиниця вимірювання, яка посилається на браузерний розмір шрифту. Але такої одиниці не існує. Найближче — це
rem
, яка посилається на базовий font-size, тільки якщо він абсолютно не змінювався.

Тобто в нашому CSS ні в якому разі не повинно бути такого коду:

/* Погано */
html { font-size: 10px; }

/* Досить погано */
:root { font-size: 16px; }

/* Задовільно, але доведеться прописати всі
ключові точки, наприклад 20rem/1.25,
40em/1.25 і т. д. */
:root { font-size: 125%; }

Безрозмірні знаменники та множники calc
Хотілося б привести
60rem - 20rem
на ширину у пікселях. Це означало б, що дріб
(x - x1) / (x2 - x1)
давала б значення в діапазоні від 0 до 1. Назвемо його
n
.

При розмірі шрифту за замовчуванням в 16 пікселів і ширини області перегляду в 600 пікселів ми отримаємо:

n = (x - x1) / (x2 - x1)
n = (600 - 320) / (960 - 320)
n = 280 / 640
n = 0.475

На жаль, не зовсім те.

Проблема в тому, що при поділі
calc()
ми не можемо в якості знаменника використовувати пікселі або яку-небудь CSS-одиницю. Величина повинна бути безрозмірною. Так що нам робити?

А що якщо просто прибрати одиниці вимірювання в знаменнику? Який буде результат обчислення
calc((100vw - 20rem)/(60 - 20))
?

Розмір шрифту — 16 пікселів
Область перегляду Ділення CSS Результат
20em (320px) (320px – 16px × 20) / (60 – 20) = 0px
40em (640px) (640px – 16px × 20) / (60 – 20) = 8px
60em (960px) (960px – 16px × 20) / (60 – 20) = 16px
Розмір шрифту — 24 пікселя
Область перегляду Ділення CSS Результат
20em (480px) (480px – 24px × 20) / (60 – 20) = 0px
40em (960px) (960px – 24px × 20) / (60 – 20) = 12px
60em (1440px) (1440px – 24px × 20) / (60 – 20) = 24px
Як бачите, в діапазоні між контрольними точками (від
20em
60em
) ми отримуємо лінійне зміна від
0rem
1rem
. Годиться!

При першій спробі змусити працювати наш CSS ми використовували множник
20px
. Треба його викреслити.

Код першої спроби:

font-size: calc( 1.25 rem + 20px * n );

Тут
n
приймала значення від 0 до 1. Але з-за обмежень синтаксису поділу в
calc()
ми не могли отримувати потрібний нам результат від 0 до 1.

Тоді ми отримали піксельний еквівалент для діапазону
0rem — 1rem
; назвемо це значення
r
.

Інше обмеження
calc()
відноситься до множення. Якщо записати
calc(a * b)
,
a
або
b
має бути безрозмірним числом.

Оскільки у
r
є розмірність (це пікселі), то безрозмірним повинен бути другий множник.

Ми хочемо збільшити на 20 пікселів у верхній контрольній точці. 20 пікселів — це
1.25 rem
, так що множник буде
1.25
:

font-size: calc( 1.25 rem + 1.25 * r );

Повинно спрацювати. Але майте на увазі, що значення
r
буде змінюватися в залежності від розміру шрифта:

  • 16 пікселів:
    1.25 * r
    одно від 0 до 20 пікселів.
  • 24 пікселя:
    1.25 * r
    одно від 0 до 30 пікселів.
Давайте тепер напишемо весь CSS-шлюз цілком, з media-запитами, верхнім і нижнім значеннями:

h1 {
font-size: 1.25 rem;
}

@media (min-width: 20em) {
/* Результат (100vw - 20rem) / (60 - 20) у діапазоні 0-1rem, 
залежно від ширини області перегляду (від 20em до 60em). */
h1 {
font-size: calc( 1.25 rem + 1.25 * (100vw - 20rem) / (60 - 20) );
}
}

@media (min-width: 60em) {
/* Права частина доповнення ПОВИННА бути rem. 
У нашому прикладі ми МОЖЕМО замінити всі оголошення
на font-size: 2.5 rem, але якщо наше базове значення
виражалося не в rem, то доведеться використовувати calc. */
h1 {
font-size: calc( 1.25 rem + 1.25 * 1rem );
}
}

У цьому випадку, на відміну від шлюзу font-size, використовує стеження, коли користувач збільшує розмір шрифту на 50%, все інше теж збільшується на 50%: базове значення, змінна і контрольні точки. Ми отримуємо діапазон 30-60 пікселів замість необхідного 20-40.



Синя лінія: font-size за замовчуванням дорівнює 16 пікселях.
Червона лінія: font-size за замовчуванням дорівнює 24 пикселів.

Можете перевірити це самостійно в першої демці, що використовує em.

Шлюзи line-height c em/rem
У нашій другій демці ми змінимо
line-height
, параграфа зі 140% до 180%. Будемо використовувати
140%
в якості базового значення, а в ролі змінної частини виступить та ж формула, що і в прикладі з
font-size
.

p {
line-height: 140%;
}
@media (min-width: 20em) {
p {
line-height: calc( 140% + .4 * (100vw - 20rem) / (60 - 20) );
}
}
@media (min-width: 60em) {
p {
line-height: calc( 140% + .4 * 1rem );
}
}

Для змінної частини
line-height
нам потрібно rem-значення, тому що
(100vw - 20rem) / (60 - 20)
дає результат в пікселях в діапазоні від
0rem
1rem
.

Оскільки
font-size
нашого параграфа залишається дорівнює
1rem
, збільшення висоти рядка ще на
40%
дорівнює
.4rem
. Це значення ми і будемо використовувати в двох
calc()
-виразах.

Тепер візьмемо з третій демки приклад
line-height
. Нам потрібно зменшити
line-height
H1 з 133,33% до 120%. І ми знаємо, що при цьому зміниться
font-size
.

Для того ж прикладу ми вже визначали, що зменшення висоти рядка можна виразити за допомогою двох контрольних точок:

  • 24 × (1.3333 - 1.2) = 3.2 px
    при нижньому розмірі області видимості,
  • 40 × (1.2 - 1.2) = 0px
    при верхньому розмірі області видимості.
В якості базового значення візьмемо
120%
, а змінна частина буде від 3,2 до 0 пікселів. Якщо розмір шрифту за замовчуванням дорівнює 16 пікселях, то 3,2 пікселя
= 0.2 rem
, так що множник дорівнює
.2
.

Нарешті, оскільки змінна частина повинна бути дорівнює нулю у верхній точці, потрібно інвертувати у формулі контрольні точки:

h1 {
line-height: calc( 120% + 0.2 * 1rem );
}
@media (min-width: 20em) {
h1 {
line-height: calc( 120% + 0.2 * (100vw - 60rem) / (20 - 60) );
}
}
@media (min-width: 60em) {
h1 {
line-height: 120%;
}
}

Два зауваження:

  1. .2rem
    єдино вірне, якщо також є шлюз font-size в діапазоні від 24 до 40 пікселів. Тут це не показано, але є у вихідному коді демки.
  2. Оскільки ми інвертуємо значення контрольних точок, обидві частини дробу
    (100vw - 60rem) / (20 - 60)
    будуть негативними для ширін області видимості менше
    60em
    і більше
    20em
    (включно). Наприклад, в нижній контрольній точці при розмірі шрифту 16 пікселів отримаємо
    640px / -40
    . А якщо в знаменник дробу чисельник і негативні, то результат буде позитивним, так що нам не потрібно змінювати знак перед множником
    0.2
    .
Висновок
Короткі підсумки. Ми розглянули дві форми CSS-шлюзів:

  • для властивостей, які можуть використовувати розмірності,
  • з прикладами для
    font-size
    та
    line-height
    ,
  • для контрольних точок, що вимірюються в пікселях і
    m
    .
Визначальний фактор — тип контрольної точки. У більшості проектів ви будете використовувати однакові точки, скажімо, для шлюзу
font-size
для зміни шаблонів. В залежності від проекту або вашої звички ключові точки можуть вимірюватися в пікселях або
m
. Особисто я віддаю перевагу пікселі, але обидва варіанти мають свої переваги.

Нагадаю: якщо ви маєте справу з media-запитами
m
, то уникайте розмірностей в пікселях при визначенні розмірів контейнерів. не ігнорувати базовий
font-size
елемента і можна використовувати єдину форму CSS-шлюзу:

@media (min-width: 20em) and (max-width: 60em) {
selector {
property: calc(
baseline_value +
multiplier *
(100vw - 20rem) / (60 - 20)
);
}
}

Тут
multiplier
— очікуване загальне збільшення значення, виражене в
rem
, але без розмірності. Наприклад: 0.75 для максимального збільшення на
0.75 rem
.

Якщо ви використовуєте media-запити в пікселях, то ви можете ігнорувати базовий
font-size
елемента. Але тоді рекомендую відсоткові значення. Також можна застосовувати дві різні форми CSS-шлюзів. Перша аналогічна
em/rem
-шлюзу, тільки зі значеннями в пікселях:

@media (min-width: 320px) and (max-width: 960px) {
selector {
property: calc(
baseline_value +
multiplier *
(100vw - 320px) / (960 - 320)
);
}
}

Тут
multiplier
— очікуване загальне збільшення значення, виражене в пікселях, а без розмірності. Наприклад:
12
для максимального збільшення на
12px
.

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

@media (min-width: 320px) and (max-width: 960px) {
selector {
property: calc(
baseline_value + 0.25 vw - 10px;
);
}
}

Тут значення
0.25 vw
та
10px
обчислені заздалегідь, ймовірно, з допомогою Sass або PostCSS.

Остання форма може бути складніше в реалізації (якщо не використовувати mixin), але завдяки більш очевидним значень здатна полегшити перевірку стилів і налагодження.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.