Перейти до вмісту

Таблиця стилів

файл, у якому лежить CSS сторінки

Що це таке

Таблиця стилів (англійською stylesheet, у специфікаціях style sheet, у розмові часто просто CSS-файл) — це набір правил CSS, який браузер завантажує, розбирає й застосовує до документа як одне ціле.

Сторінка може отримати таблицю стилів чотирма шляхами. Елемент link з rel="stylesheet" вказує на окремий файл. Елемент style містить правила просто всередині HTML. Правило @import на початку однієї таблиці стилів підтягує іншу. А скрипт може створити таблицю через new CSSStyleSheet() і приєднати її до документа чи shadow root через adoptedStyleSheets. Перші два способи в head сторінки виглядають так:

<link rel="stylesheet" href="/css/site.css" />
<link rel="stylesheet" href="/css/print.css" media="print" />
<style>
    .banner {
        border-block-end: 2px solid;
    }
</style>

Таблиці стилів автора сайту — не єдині учасники. Кожен браузер має власну типову таблицю стилів, тому заголовок без жодного оформлення все одно виходить великим і жирним, а читач може додати власну таблицю стилів користувача; каскад зважує ці три походження одне проти одного. Атрибут style на окремому елементі — інша річ: оголошення без селектора, які належать цьому елементу, а не таблиці.

Після завантаження браузер подає таблицю стилів як об’єкт CSSStyleSheet у CSS Object Model — специфікації, з якої походить визначення цього терміна. Об’єкт зберігає елемент-власника, медіа, до яких таблиця застосовується, її заголовок, ознаку вимкнення та список правил, а document.styleSheets перелічує таблиці сторінки по порядку. Один прапорець існує заради приватності: таблиця, отримана з іншого походження (origin) без CORS, не має позначки origin-clean, і скрипт, який просить її правила, отримує натомість помилку безпеки.

Атрибут media обмежує таблицю стилів певним середовищем. Таблицю з media="print" браузер усе одно завантажує, але чекає лише на ті таблиці, чиї медіа збігаються, тож екран не чекає на таблицю для друку.

Чому це важливо

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

Перше рішення — скільки файлів. Один пакет, спільний для всіх сторінок, завантажується раз і далі береться з кешу; окрема таблиця для кожної сторінки надсилає менше на початку й більше, коли читач переходить сайтом. Правила, вбудовані в елемент style, не потребують окремого запиту, але їдуть знову з кожною сторінкою, бо їх неможливо кешувати окремо від документа, що їх несе. Кожен @import додає цілий мережевий обмін, бо браузер дізнається імпортовану адресу лише після того, як прибуде файл, що її імпортує.

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

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

Як це виглядає на практиці

Власний пакет таблиць стилів сайту показує, наскільки один вбудований ресурс може переважити все інше. На 87 КБ він був, для документного сайту без фреймворку, більшою частиною ваги сторінки, витраченою ще до прибуття будь-якого вмісту, і дві третини найбільшої таблиці стилів виявилися одним шрифтом, закодованим у base64: приблизно 56 КБ тексту, що кодували приблизно 42 КБ шрифту. Його вбудували заради виграшу в першому промальовуванні, який попередньо завантажений зовнішній файл дає й так, і кожна сторінка платила за нього незалежно від того, чи була потрібна та вага. Його вилучення забрало цей файл з 84 КБ до 28, а весь пакет — з 87 КБ до 31 КБ, і насиченості шрифту натомість попередньо завантажуються. Base64 записує кожні три байти чотирма символами, тож двійковий файл, вбудований у таблицю стилів, завжди важить приблизно на третину більше за той самий файл, відданий окремо.

Папір теж є носієм, якому таблиця стилів мусить служити. Кейси сайту — це те, що хтось роздруковує й несе на зустріч, тож одна таблиця стилів на 637 рядків з єдиним правилом сторінки й одним блоком друку дає їм друкуватися на невідомому папері в обох колірних темах. Правило сторінки задає поле й навмисно не задає розміру паперу, а міра приблизно у сімдесят два символи не дає альбомному аркушу розігнати рядок приблизно до ста десяти. Чотири види поведінки рушіїв записано як пастки, і кожен спричинив справжній дефект: одиниці вікна перегляду, що розв’язуються в коробку сторінки в одному рушії й у вікно в іншому, через що обрізалася третина кожного рядка; елементи з фіксованим положенням, які малюються на кожному аркуші; палітра, що для темної теми усталено бере білий текст і надрукувала чотири підзаголовки розділів білим по білому; і гнучка чи сіткова коробка, яку сторінковий носій не може розділити, що дало порожній перший аркуш. Перевірка з шести частин охоплює шість комбінацій паперу й орієнтації, обидві вподоби колірної схеми та справжню кількість сторінок, і саме вона помітила, що випадковий термінатор коментаря змусив лінтер CSS проковтнути ціле правило і двічі пройти чисто.

Сторінка безпеки сайту називає Subresource Integrity для таблиці стилів: під час збирання пакет отримує відбиток і хеш, тож браузер читача може перевірити, що він надійшов без змін. Та сама сторінка пропонує читачеві порахувати хости в панелі мережі браузера й дає відповідь: він один, і це хост самого сайту.

Де виникають проблеми

Таблиця стилів, про яку браузер дізнається пізно, і прибуває пізно. Посилання в кінці body, таблиця, вставлена скриптом, або ланцюжок правил @import — кожне з них дає сторінці промалюватися раніше, ніж прибудуть стилі, тож сирий текст перескакує в макет або готовий макет зсувається. Ланцюжок імпортів у джерелі виглядає невинно:

/* site.css: браузер знайде base.css лише після прибуття цього файла */
@import url("base.css");
@import url("theme.css");

Ставте посилання на таблиці стилів у head і вбудовуйте імпорти в пакет під час збирання.

Розгортання може звести нову розмітку зі старими правилами. Читачі, чий кеш тримає попередню таблицю стилів, отримують HTML, що чекає на класи, про які старий файл ніколи не чув, і макет у них ламається, доки не спливе термін кешу, тоді як кожен, хто тестує зі свіжим кешем, бачить робочий сайт. Причина — ім’я таблиці стилів, яке ніколи не змінюється, разом із довгим терміном кешування, і ім’я з відбитком її усуває.

Таблицю стилів, віддану з неправильним типом вмісту, браузер ігнорує. Стандарт HTML застосовує підключену таблицю стилів лише тоді, коли вона приходить як text/css, з одним винятком для файла з того самого походження на сторінці в режимі quirks. Сервер, який позначає файли .css як text/plain або відповідає на відсутній файл HTML-сторінкою з успішним статусом, лишає сторінку без стилів, хоча панель мережі показує запит, що начебто вдався. Перевіряйте заголовок розгорнутого файла.

Хеш цілісності застаріває, коли файл змінюється після хешування: ручна правка на сервері, CDN, що мініфікує файл дорогою, або збирання, яке хешує файл перед останнім переписуванням. Браузер вважає розбіжність мережевою помилкою й показує сторінку без таблиці стилів. З іншого походження цілісність потребує ще й CORS: атрибута crossorigin на посиланні й відповідного заголовка від сервера. Обчислюйте хеш на тому кроці, що записує остаточний файл.

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

Що почитати далі

  • CSS Object Model (CSSOM) — що таке таблиця стилів усередині браузера та інтерфейс, яким скрипти її читають і змінюють.
  • HTML Standard: Links — тип посилання stylesheet, правило щодо типу вмісту й те, коли таблиця стилів блокує рендеринг.
  • Subresource Integrity — атрибут integrity і те, що браузер робить із файлом, який не пройшов перевірку.
  • Fetch Standard: HTTP cache partitions — як ключ кешу будується від сайту верхнього рівня.
  • CSS Paged Media Module Level 3 — правило @page і поля сторінки, з якими працює таблиця стилів для друку.
  • Media Queries Level 5 — друк, екран, вподоби колірної схеми та інші умови, які може перевіряти атрибут media.

Словник