Від 66% до 185px: як браузери обчислюють значення CSS перед формуванням макету
Простежіть значення CSS на етапах: оголошення, каскадування, визначення, обчислення, використання та фактичне значення, і зрозумійте, чому відносні одиниці та трик перетворення rem поводяться саме так.
Перемога в каскадному процесі не є кінцем шляху CSS-декларації. Якщо width: 50% перемагає всі конкуруючі правила, браузер все одно не може передати значення 50% своєму механізму формування макету; йому потрібно визначити, що саме становить п’ятдесят відсотків у поточному контексті, та перетворити це на конкретний розмір. Цей посібник простежує одне значення через кожну стадію обробки, визначену специфікацією, показує, де вирішуються відносні одиниці, такі як rem, та відсотки, а також пояснює популярний трик перетворення px у rem разом із проблемами доступності.
Значення, яке ви вводите, — це не те значення, яке буде використано
Більша частина того, що ви вводите у таблицю стилів, — це співвідношення, а не вимірювання. Ширина може бути частиною ширини батьківського елемента:
width: 50%;
або, у іншому правилі, іншою частиною:
width: 66%;
Розмір шрифту може бути відносним до розміру шрифту кореневого елемента:
font-size: 2rem;
А висота може бути частиною розмірів вікна перегляду:
height: 50vh;
Жоден з цих показників не вказується у пікселях, проте для формування макету необхідні реальні розміри, щоб визначити місце розташування кожного елемента. Щоб подолати цю прогалину, кожне значення проходить через фіксовану послідовність етапів:
Declared Value
↓
Cascaded Value
↓
Specified Value
↓
Computed Value
↓
Used Value
↓
Actual Value
Спочатку назви цих етапів здаються спеціалізованою термінологією, але кожен етап відповідає на конкретне запитання. У розділах нижче вони розглядаються по черзі за допомогою одного прикладу.
Оголошене значення: те, про що ви просили
Оголошене значення — це просто те значення, яке зустрічається в оголошенні. Дано:
width: 66%;
Оголошене значення для width становить 66%. Це лише запит, більше нічого. Елемент може мати кілька оголошених значень для однієї властивості, отриманих з різних правил, тому першою задачею є вибір одного з них.
Каскадове значення: те, що залишилося
p {
width: 100px;
}
а інше — клас:
.amazing {
width: 66%;
}
Якщо абзац має клас amazing, обидва правила підходять до нього. Селектор класу є більш специфічним, ніж селектор елемента без класу, тому каскад обирає:
width: 66%;
Це залишене 66% і є каскадовим значенням. Цей крок має велике значення, оскільки кожна наступна стадія працює лише з одним переможцем; правила, які програли, відкидаються, а не обробляються.
Вказане значення: переконання, що кожна властивість має значення
Вказане значення — це результат забезпечення того, що кожна властивість кожного елемента має яке-небудь значення. Правило просте:
- якщо існує каскадове значення, воно стає вказаним значенням;
У нашому прикладі існує каскадне значення, тому вказане значення залишається 66%, і цей етап, здається, нічого не робить. Це стає важливим для властивостей, щодо яких зовсім не вказано жодних правил, саме тут і входить у гру спадкування. Цей механізм розглядається у останньому розділі.
Обчислене значення: визначення того, що можна визначити без формування макету
Обчислене значення — це те, де браузер визначає все, що можна з’ясувати без фактичного формування макету сторінки. Відносні довжини, засновані на розмірах шрифту, перетворюються на абсолютні довжини, а ключові слова — на конкретні значення. Розгляньмо:
font-size: 2rem;
Одиниця вимірювання тут:
2rem
Описує співвідношення „у два рази більший розмір шрифту кореневого елемента“, а не фіксоване значення. На етапі обчислень браузер знаходить розмір шрифту кореневого елемента та перетворює це значення на пікселі. Якщо розмір шрифту кореневого елемента становить 16 пікселів, то обчислений розмір шрифту буде 32 пікселі.
Однією з причин такого перетворення на цьому етапі є механізм успадкування: саме обчислене значення успадковують дочірні елементи, тож вони отримують конкретну довжину замість співвідношення, яке мусили б переосмислити у власному контексті. Саме тому розміри шрифту типу em, розташовані один всередині іншого, посилюють один одного, а відсоткові значення висоти рядка іноді призводять до того, що текст у дочірніх елементах з більшим шрифтом виглядає стиснутим.
Деякі значення ще не можна визначити. Ширина у відсотках залежить від розміру блоку, який її містить, а цей розмір відомий лише після формування макету, тому 66% зазвичай залишається відсотком у обчисленому значенні та буде остаточно визначений на наступному етапі.
Використане значення: інформація про макет
Використане значення — це результат після отримання інформації про макет. Повернутися до:
width: 66%;
Одного лише відсотка недостатньо, поки не відомо, частку від чого він становить. Для width цим орієнтиром є ширина блоку, який його містить. Припустимо, що батьківський елемент є:
280px
широким. Тепер браузер може обчислити:
66% of 280px
що дає наступний результат:
184.8px
Це значення є фактично використаним. Це є поворотним моментом у процесі: браузер більше не використовує вказаний вами відсоток, а справжню величину, яка визначається за реальною геометрією сторінки. Те саме стосується значень типу auto для ширини, які мають сенс лише після визначення оточуючого макету. Якщо ви запитуєте елемент у JavaScript за допомогою getComputedStyle, багато властивостей, залежних від макету, таких як width, повертають саме це вирішене значення у пікселях, а не відсоток, що є зручним способом спостерігати цей процес на практиці.
Фактичне значення: врахування обмежень пристрою
Останній етап враховує обмеження середовища відображення. Пристрій не завжди може намалювати значення з довільною точністю, тому фактично використане значення може бути:
184.8px
у кінцевому підсумку відображене приблизно так:
185px
Це скориговане значення є фактичною величиною. На практиці сучасні браузери відображають контент з точністю до пікселя та застосовують округлення або інші корекції у різних моментах залежно від властивостей та движка, тому не варто покладатися на певні правила округлення. Головна ідея полягає у тому, що може відбутися фінальна корекція між обчисленим значенням та тим, що відображається на екрані.
Якщо подивитися ширше, вся ця послідовність зводиться до одного речення: те, що ви пишете у стилівому файлі, не обов’язково є тим, що браузер насправді використовує, і між цими етапами існують чітко визначені процедури.
Чому відносні одиниці стають менш загадковими
Ця модель пояснює, чому відносні одиниці є настільки корисними та чому їх так часто неправильно розуміють. Одиниці типу цих:
%
rem
em
vh
vw
Це дозволяє описувати розміри за допомогою інших параметрів (розміру батьківського елемента, базового розміру шрифту, власного розміру шрифту елемента чи області перегляду) замість жорсткого використання пікселів. Саме це дозволяє макетам адаптуватися до різних екранів та налаштувань користувача.
Кожен з них, проте, має бути обчислений відносно свого посилання, перш ніж його може використати макет, і це відбувається на різних етапах: одиниці, відносні до шрифту, — на етапі обчислень, а відсотки — відносно контейнерного блоку, — на етапі використання. Враховуючи цей процес, можна замінити розпливчасту думку про те, що «браузер все з’ясує сам», на більш конкретне запитання під час дебагування: від чого залежить це значення та яким є зараз це посилання? Коли респонзивний макет поводиться некоректно, причиною часто є посилання, яке відрізняється від того, що ви припускали, наприклад, відсоток, який обчислюється відносно неочікуваного контейнерного блоку, або em, який компенсується через кілька вкладених елементів.
Перетворення px у rem з коренем 10px
Розуміння обробки значень також прояснює поширений підхід до визначення розмірів: перетворення значень пікселів з дизайну у rem. Припустимо, дизайн вказує:
padding: 30px;
а ви хочете виразити це так:
padding: 3rem;
Популярним підходом є встановлення розміру шрифту кореня на кругле число:
html {
font-size: 10px;
}
Після цього:
1rem = 10px
а перетворення стають арифметичними операціями в уяві:
30px → 3rem
40px → 4rem
20px → 2rem
Загалом:
rem = pixels / 10
Перевага полягає не лише у більш охайних числах. Оскільки кожен rem визначається відносно розміру шрифту кореня на етапі обчислень, зміна цього значення одночасно змінює розміри всіх елементів, виміряних у rem, що дає можливість контролювати всю систему розмірів єдиним способом.
Існує важлива застереження. Встановлення значення кореня на фіксовані 10px переважує значення розміру шрифту за замовчуванням, яке користувач міг обрати у налаштуваннях браузера, що підриває одну з основних причин використання rem з точки зору доступності. Широко використовуваною альтернативою є html { font-size: 62.5%; }: при за замовчуванням 16px у браузері це все одно дає 10px, але розмір шрифту масштабується пропорційно, коли користувач збільшує бажаний розмір. Якщо ви оберете цю схему, не забудьте також встановити читабельний розмір шрифту для body (наприклад, 1.6rem), оскільки інакше текст буде відображатися за замовчуванням у зменшеному розмірі кореня.
Коли нічого не було оголошено
Одне питання залишилося без відповіді на попередніх етапах. Розгляньмо таку маркувальну структуру:
<div class="parent">
<p>Hello World</p>
</div>
та правило для обгортки:
.parent {
color: red;
}
Ніде немає правила на кшталт:
p {
color: red;
}
Проте текст абзацу червоний. У цьому абзаці немає каскадування властивості color, тому вказане значення мусить походити з іншого джерела. Оскільки color є успадкованою властивістю, браузер використовує обчислене значення кольору батьківського елемента. Це і є механізм успадкування, який безпосередньо впливає на етап визначення значень, описаний раніше. Щоб детальніше дізнатися, які властивості успадковуються, чому дочірні елементи отримують обчислені значення замість оголошених, та як параметри inherit та initial змінюють стандартні налаштування, успадкуванню потрібне окреме розглядання.
Ключові висновки
- Значення CSS проходить шість етапів: оголошене, каскадоване, вказане, обчислене, використане та фактичне.
- Каскадування зводить багато оголошених значень до одного каскадованого; лише це значення подальшо обробляється.
rem та em, на етапі обчислень стають абсолютними довжинами; відсоткові ширини очікують на момент розташування елементів та визначаються на етапі їх використання.62.5% краще враховує уподобання користувача щодо шрифту, ніж фіксоване значення 10px.