Обробка помилок CSS

Коли в CSS є помилка, така як недійсне значення чи пропущена крапка з комою, то замість викидання помилки, як у JavaScript, браузер (або інший користувацький агент) м'яко відновлюється. Браузери не надають сповіщень, пов'язаних з CSS, і не позначають помилки, що трапилися в стилях, жодним іншим чином. Вони просто відкидають недійсний вміст і розбирають наступні дійсні стилі. Це перевага CSS, а не вада.

Цей посібник описує те, як розбирачі CSS відкидають недійсний CSS.

Помилки розбирача CSS

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

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

div {
  display: inline-flex;
  display: inline flex;
}

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

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

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

Помилки директив

Символ @, відомий у специфікаціях CSS як <at-keyword-token>, вказує на початок директиви CSS. Коли символом @ почалась директива, то з точки зору розбирача ніщо не вважається недійсним. Все, аж до першої крапки з комою (;) або початкової фігурної дужки ({) належить до вступної частини директиви. Вміст кожної директиви тлумачиться згідно з правилами граматики для конкретної директиви.

Директиви-інструкції, як то оголошення @import і @namespace, містять лише вступну частину. У випадку звичайних директив, крапка з комою зразу їх закінчує. Якщо вміст вступної частини є недійсним згідно зі граматикою конкретної директиви, то така директива ігнорується, а браузер розбирає CSS далі, після того, як зустрів наступну крапку з комою. Наприклад, якщо директива @import зустрічається після будь-якого оголошення CSS, крім @charset, @layer або інших директив @import, то така директива @import ігнорується.

@import "assets/fonts.css" layer(fonts);
@namespace svg url(http://www.w3.org/2000/svg);

Якщо розбирач зустрічає фігурну дужку (}) раніше, ніж крапку з комою, то така директива розбирається як блокова директива. Блокові директиви, також відомі як директиви вкладення, як то @font-face і @keyframes, містять блок оголошень, оточений фігурними дужками ({}). Початкова фігурна дужка сповіщає браузер про те, де закінчується вступна частина директиви та починається її тіло. Розбирач зазирає вперед, шукаючи відповідні блоки (вміст, оточений (), {} або []), поки не знайде фігурну дужку (}), яка не відповідає жодній іншій фігурній дужці: вона закриває тіло директиви.

Різні директиви мають різні граматичні правила, різні (або жодних) дескриптори, а також різні правила щодо того, що, якщо щось узагалі, робить усю директиву недійсною. Очікувана граматика для кожної директиви та те, як обробляються помилки, документовані на сторінках відповідних директив. Обробка недійсного вмісту залежить від конкретної помилки.

Наприклад, правило @font-face вимагає і дескриптора font-family, і дескриптора src. Якщо один з них відсутній або недійсний, то вся директива @font-face є недійсною. Додавання невідповідного дескриптора, або додаткового дійсного дескриптора шрифту з недійсним значенням, або оголошення стилю властивості всередині вкладеного блоку @font-face не зробить оголошення шрифту недійсним. Якщо назва шрифту та його джерело – додані та дійсні, то будь-який недійсний CSS всередині директиви ігнорується, але блок @font-face все одно розбирається.

Попри те, що граматика директиви @keyframe вельми відрізняється від граматики директиви @font-face, тип помилки все ж впливає на те, що ігнорується. Важливі оголошення (ті, що мають позначку important) і властивості, що не можуть бути анімовані, ігноруються серед правил ключового кадру, але вони не впливають на інші стилі, оголошені в тому самому блоку селектора ключового кадра. Додання недійсного селектора ключового кадра (наприклад, відсоткового значення, меншого ніж 0% або більшого ніж 100%, або числа без %) робить недійсним список селекторів ключового кадру, і таким чином – ігнорується ввесь блок стилю. Недійсний селектор ключового кадру робить недійсним лише блок стилю недійсного селектора; він не робить недійсним усе оголошення @keyframe. Додавання стилів між двома блоками селекторів ключових кадрів, з іншого боку, робить недійсною всю директиву @keyframe.

Частина директив майже завжди є дійсною. Директива @layer має і звичайну форму, і форму зі вкладеністю. Синтаксис інструкції @layer містить лише вступну частину, що закінчується крапкою з комою. Інший варіант: при синтаксисі зі вкладеністю стилі шару вкладаються в фігурні дужки, що стоять після вступної частини. Пропуск кінцевої фігурної дужки може бути логічною помилкою, але не є синтаксичною. У випадку пропуску кінцевої дужки в @layer, усі стилі, що стоять після місця, де повинна була б бути кінцева дужка, розбираються як стилі в каскадному шарі, визначеному в вступній частині директиви. CSS є дійсним, оскільки немає синтаксичних помилок; нічого не відкидається. Синтаксична помилка може призвести до того, що названий або безіменний шар буде порожнім, але цей шар все одно створюється.

Помилки у списках селекторів

Є чимало способів зробити помилку, пишучи селектор, але лише недійсні селектори роблять список селекторів недійсним (дивіться недійсний список селекторів).

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

Якщо список селекторів містить хоча б один недійсний селектор, то ввесь блок стилів ігнорується. Є винятки: якщо недійсний селектор знаходиться всередині псевдокласу :is або :where (які приймають поблажливі списки селекторів), або якщо невідомий селектор є псевдоелементом з префіксом -webkit-, то ігнорується лише невідомий селектор, який не дає жодних збігів. Такий список селекторів не стає недійсним.

Коли не рахувати цих винятків, то один-єдиний селектор, що є недійсним або не підтримується, робить недійсним усе правило, і ввесь блок селектора ігнорується. Потім браузер шукає кінцеву фігурну дужку та продовжує розбір після неї.

Виняток -webkit-

У зв'язку з історичними проблемами зловживання браузерно-специфічними префіксами в селекторах і назвах властивостей (а також їх значеннях), браузери уникають надмірного відкидання списків селекторів, розглядаючи всі псевдоелементи, що починаються з нечутливого до регістра префікса -webkit- і не закінчуються на (), як дійсні.

Це означає, що псевдоелемент виду ::-webkit-works-only-in-samsung не зробить недійсним список селекторів, незалежно від того, в якому браузері виконується код. У таких випадках псевдоелемент може не бути впізнаний і не підтримуватися браузером, але він не призведе до того, що весь список селекторів і пов'язаний з ним блок стилів буде ігноруватися. З іншого боку, невідомий селектор з префіксом із функційним записом ::-webkit-imaginary-function() зробить недійсним весь список селекторів, і браузер ігноруватиме весь блок такого селектора.

Помилки всередині блоків оголошень CSS

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

Цей приклад містить помилку. Розбирач ігнорує помилку (а також коментарі), шукаючи далі, поки не зустріне крапку з комою, а тоді перезапускає розбір:

p {
/* Недійсний синтаксис, оскільки пропущена крапка з комою */
  border-color: red
  background-color: green;

/* Дійсний синтаксис, але, ймовірно, логічна помилка */
  border-width: 100vh;
}

Причина того, що перше оголошення в цьому блоку селектора є недійсним, полягає в тому, що крапки з комою немає, і оголошення не є останнім у блоку селектора. Властивість, у якої немає крапки з комою, ігнорується, так само як і пара властивість-значення, що йде за нею, оскільки браузер продовжує розбір лише після крапки з комою або кінцевої дужки. Зокрема, значення border-color розбирається як red background-color: green;, що не є дійсним значенням <color>.

Значення border-width 100vh, ймовірно, є помилкою, але не є помилкою розбирача. Оскільки воно синтаксично дійсне, то воно розбирається та застосовується до елементів, що відповідають селектору.

Префікси постачальників

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

Можна зустріти історичний CSS, що має наступний вигляд:

/* Значення з префіксами */
.wrapper {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  display: block flex;
}
/* Властивості з префіксами */
.rounded {
  -webkit-border-radius: 50%;
  -moz-border-radius: 50%;
  -ms-border-radius: 50%;
  -o-border-radius: 50%;
  border-radius: 50%;
}

У цьому прикладі останнє оголошення в кожному блоку є дійсним у всіх браузерах – display: flex; і border-radius: 50%;. Завдяки каскадному порядку появи браузери застосовують оголошення з префіксами, які розуміють, а тоді відкидають ці значення на користь стандартної версії без префікса.

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

Помилки з самозакривальними закінченнями

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

Примітка: Це істинно для зовнішніх списків стилів, блоків селекторів усередині елемента HTML <style> та вбудованих правил в атрибуті style.

Якщо вміст між останньою крапкою з комою та кінцем списку стилів є дійсним, навіть коли неповним, то CSS буде розібрано нормально. Наприклад, якщо не закрити оголошення @keyframe перед закриттям <style>, то анімація все одно буде дійсною.

<style>
@keyframes move {
  100% {
    transform: translatex(100vw)
</style>

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

Незакриті коментарі

Незакриті коментарі є логічними помилками, але не синтаксичними. Якщо коментар починається з /*, але не закривається, то весь код CSS до кінцевого обмежувача (*/) у наступному коментарі або до кінця списку стилів, залежно від того, що зустрінеться раніше, є частиною цього коментаря. Попри те, що незакритий коментар не робить CSS недійсним, він призводить до ігнорування CSS після початкового обмежувача (/*).

<style>
  /* цей коментар – незакритий
  @keyframes move {
    0% {transform: translatex(0);}
    100% {transform: translatex(100vw);}
  }
</style>
<p style="/* ще один незакритий коментар">Розбирається як HTML.</p>

У цьому прикладі два коментарі CSS не закриті, але кінцевий тег </style> закриває перший коментар, а кінцева лапка атрибута style закриває другий коментар.

Граматична перевірка

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

Кожна властивість CSS приймає різні типи даних. Наприклад, властивість background-color приймає або дійсний <color>, або глобальне ключове слово CSS. Коли значення, призначене властивості, має неправильний тип, наприклад, background-color: 45deg, то оголошення є недійсним і, отже, ігнорується.

Недійсні кастомні властивості

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

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

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

Погляньмо на приклад, що ілюструє цю логіку:

:root {
  --theme-color: 45deg;
}
body {
  background-color: var(--theme-color);
}

У коді вище оголошення кастомної властивості є дійсним. Оголошення background-color також є дійсним під час обчислення. Проте коли браузер підставляє кастомну властивість у var(--theme-color) з 45deg як значенням властивості background-color, то граматика є недійсною. кут не є дійсним значенням background-color. У цьому випадку оголошення не ігнорується як недійсне. Замість цього, коли кастомна властивість має неправильний тип, то якщо вона піддається успадкуванню, то значення успадковується від батьківського елемента. Якщо ж вона успадкуванню не піддається, то використовується усталене початкове значення. У випадку background-color значення властивості не є успадкованим, тому використовується початкове значення transparent.

Щоб контролювати краще те, як кастомні властивості відступають, можна скористатися директивою @property, щоб визначити початкове значення властивості:

@property --theme-color {
  syntax: "<color>";
  inherits: false;
  initial-value: rebeccapurple;
}

Дивіться також