CSS 错误处理

当 CSS 中存在错误(例如无效值或缺少分号)时,浏览器(或其他用户代理)不会像 JavaScript 中那样抛出错误,而是会优雅地恢复。浏览器不会提供与 CSS 相关的警报,也不会以其他方式指示样式中发生了错误。它们只是丢弃无效内容并解析后续的有效样式。这是 CSS 的特性,而不是错误。

本指南讨论了 CSS 解析器 如何丢弃无效 CSS。

CSS 解析器错误

当遇到 CSS 错误时,浏览器的 解析器 会忽略包含错误的行,丢弃最少的 CSS 代码,然后恢复到正常 解析 CSS。 “错误恢复”只是忽略或跳过无效内容。

浏览器忽略无效代码的事实使得能够使用新的 CSS 功能,而无需担心在旧版浏览器中出现任何问题。浏览器可能无法识别新功能,但这没关系。丢弃无效内容而不抛出错误允许新旧语法在同一规则集中共存,但请记住,应按此顺序指定它们。例如

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

display 属性既接受传统的单值语法,也接受 多关键字语法。浏览器将呈现旧语法,直到它们将新语法识别为有效,此时新语法将覆盖旧语法。如果用户使用的是旧版浏览器,则有效的回退不会被新的 CSS 覆盖,因为浏览器将其视为无效。

浏览器因错误而忽略的 CSS 类型和数量取决于错误的类型。下面列出了一些常见的错误情况

在解析每个声明、样式规则、@规则等之后,浏览器会根据其预期针对该构造的语法检查解析的内容。如果内容与该构造的预期语法不匹配,则浏览器将其视为无效并忽略它。

@规则错误

@ 符号,在 CSS 规范中称为 <at-keyword-token>,表示 CSS at-rule 的开头。一旦@规则以@符号开头,从解析器的角度来看,任何内容都不会被认为是无效的。直到第一个分号 (;) 或开花括号 ({) 之前的所有内容都是@规则的前导部分。每个@规则的内容都根据该特定@规则的语法规则进行解释。

语句@规则,例如@import@namespace 声明,仅包含前导部分。分号立即结束语句@规则的@规则。如果前导部分的内容根据该@规则的语法无效,则@规则将被忽略,浏览器将在遇到下一个分号后继续解析 CSS。例如,如果@import @规则出现在除@charset@layer或其他@import语句之外的任何 CSS 声明之后,则@import声明将被忽略。

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

如果解析器在遇到分号之前遇到花括号 ({),则@规则将被解析为块@规则。块@规则(如@font-face@keyframes)包含由花括号 ({}) 括起来的一组声明。开花括号通知浏览器@规则前导部分在哪里结束以及@规则的主体在哪里开始。解析器向前查找,寻找匹配的块(由(){}[]括起来的内容),直到找到一个不与任何其他花括号匹配的闭花括号 (}):这将关闭@规则的主体。

不同的@规则具有不同的语法规则、不同的(或没有)描述符以及不同的规则来确定什么(如果有)将使整个@规则无效。每个@规则的预期语法以及如何处理错误都记录在相应的@规则页面上。无效内容的处理方式取决于错误。

例如,@font-face规则需要font-familysrc描述符。如果省略或无效,则整个@font-face规则无效。包含不相关的描述符、任何其他具有无效值的有效字体描述符或@font-face嵌套块中的属性样式声明都不会使字体声明无效。只要包含字体名称和字体源且有效,则@规则中任何无效的 CSS 都会被忽略,但@font-face块仍将被解析。

虽然@keyframe @规则的语法与@font-face规则的语法非常不同,但错误类型仍然会影响忽略的内容。重要声明(用important标记)和无法进行动画处理的属性在关键帧规则中会被忽略,但它们不会影响在同一关键帧选择器块中声明的其他样式。包含无效的关键帧选择器(例如小于0%或大于100%的百分比值,或省略%<number>)会使关键帧选择器列表无效,因此样式块将被忽略。无效的关键帧选择器只会使无效选择器的样式块无效;它不会使整个@keyframe声明无效。另一方面,在两个关键帧选择器块之间包含样式将使整个@keyframe @规则无效。

某些@规则几乎总是有效的。@layer @规则以常规形式和嵌套形式出现。@layer语句语法仅包含前导部分,以分号结尾。或者,嵌套语法在prelude之后具有在花括号之间嵌套的层样式。省略闭花括号可能是逻辑错误,但不是语法错误。在@layer中缺少闭花括号的情况下,闭花括号应该出现之后的所有样式都被解析为在@规则prelude中定义的级联层中。CSS 有效,因为没有语法错误;没有任何内容被丢弃。语法错误可能会导致命名或匿名层为空,但层仍将创建。

选择器列表中的错误

编写选择器时可能会犯很多错误,但只有无效的选择器会导致选择器列表无效(参见无效选择器列表)。

如果您为不存在的类、id 或元素(或自定义元素)包含classidtype选择器,这可能是逻辑错误,但不是语法错误。但是,如果您在伪类或伪元素中出现错别字,则可能会创建无效的选择器,这是解析器需要解决的错误。

如果选择器列表包含任何无效的选择器,则整个样式块将被忽略。有一些例外:如果无效的选择器位于:is:where伪类(接受宽容选择器列表)内,或者未知的选择器是-webkit-前缀的伪元素,则仅忽略未知的选择器,因为它不匹配任何内容。选择器列表不会失效。

在这些例外情况之外,选择器列表中单个无效或不受支持的选择器将使整个规则无效,并且整个选择器块将被忽略。然后浏览器将查找闭花括号并从该点继续解析。

-webkit- 异常

由于选择器和属性名称(和值)中过度使用浏览器特定前缀而导致的遗留问题,浏览器通过将所有以不区分大小写的-webkit-前缀开头且不以()结尾的伪元素视为有效来避免过度使选择器列表无效。

这意味着像::-webkit-works-only-in-samsung这样的伪元素不会使选择器列表无效,无论代码在哪个浏览器中运行。在这种情况下,伪元素可能无法被浏览器识别或支持,但它不会导致整个选择器列表及其关联的样式块被忽略。另一方面,带有函数表示法的未知前缀选择器::-webkit-imaginary-function()将使整个选择器列表无效,浏览器将忽略整个选择器块。

CSS 声明块中的错误

在声明块中的 CSS 属性和值方面,如果属性或值无效,则该属性值对将被忽略和丢弃。当用户代理解析或解释声明列表时,任何点的未知语法都会导致浏览器的解析器仅丢弃当前声明。然后它在遇到下一个分号或闭花括号后继续解析 CSS,以先出现的为准。

此示例包含错误。解析器忽略错误(以及注释),向前查找直到遇到分号,然后重新开始解析

css
p {
/* Invalid syntax due to  missing semi-colon */
  border-color: red
  background-color: green;

/* Valid syntax but likely a logic error */
  border-width: 100vh;
}

此选择器块中第一个声明无效的原因是缺少分号,并且该声明不是选择器块中的最后一个声明。缺少分号的属性会被忽略,其后的属性值对也会被忽略,因为浏览器只在遇到分号或闭括号后才会继续解析。具体来说,border-color的值被解析为red background-color: green;,这不是有效的<color>值。

border-width值为100vh可能是错误,但不是错误。由于它在语法上有效,因此它将被解析并应用于与选择器匹配的元素。

供应商前缀

当浏览器不理解供应商前缀的属性名称和属性值时,将被视为无效并忽略。仅忽略包含无效属性或值的单个规则。解析器查找下一个分号或闭花括号,然后从那里继续解析。

您可能会遇到如下所示的旧版 CSS

css
/* Prefixed values */
.wrapper {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  display: block flex;
}
/* Prefixed properties */
.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 也会正常解析。例如,如果您在关闭<style>之前未能关闭@keyframe声明,则动画仍然有效。

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

这里move动画有效。未能正确关闭 CSS 语句并不一定会使语句无效。也就是说,不要利用 CSS 的宽容性。始终关闭所有语句和样式块。这使您的 CSS 更易于阅读和维护,并确保浏览器按照您的意图解析 CSS。

未关闭的注释

未关闭的注释是逻辑错误,而不是语法错误。如果注释以/*开头但未关闭,则所有 CSS 代码(直到后续注释中的结束分隔符 (*/) 或样式表末尾,以先出现的为准)都是注释的一部分。虽然未关闭的注释不会使您的 CSS 无效,但它会导致在开头分隔符 (/*) 之后出现的 CSS 被忽略。

html
<style>
  /* this comment is not closed
  @keyframes move {
    0% {transform: translatex(0);}
    100% {transform: translatex(100vw);}
  }
</style>
<p style="/* another unclosed comment">Parsed as HTML.</p>

在此示例中,两个 CSS 注释都没有关闭,但结束的</style>标签关闭了第一个注释,而style属性的结束引号关闭了第二个注释。

语法检查

在解析每个声明、样式规则、@规则等之后,用户代理会检查语法是否遵循该声明的规则。例如,如果属性值的数据类型错误或描述符对于所描述的@规则无效,则不匹配预期语法的內容将被视为无效并被忽略。

每个 CSS 属性都接受特定的数据类型。例如,background-color 属性接受有效的<color>或 CSS 全局关键字。当分配给属性的值类型错误时,例如background-color: 45deg,则声明无效,因此被忽略。

无效的自定义属性

自定义属性在声明时通常被认为是有效的,但在访问时可能会创建无效的 CSS,即它们可能用作(通过 var() 函数)不支持该值类型的属性的值。浏览器在遇到每个自定义属性时都会对其进行解析,而不管属性在何处被使用。

通常,当属性值无效时,声明会被忽略,并且该属性回退到最后一个有效值。但是,无效的计算自定义属性值的工作方式略有不同。

var() 替换无效时,声明不会被忽略,并且会使用该属性的 初始 值或 继承 值。属性被设置为一个新值,但可能不是预期值。

让我们看一个例子来说明这种行为

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

在上面的代码中,自定义属性声明是有效的。background-color 声明在计算时也是有效的。但是,当浏览器将自定义属性中的 var(--theme-color) 替换为 45deg 作为 background-color 属性的值时,语法无效。<angle> 不是有效的 background-color 值。在这种情况下,声明不会被视为无效而忽略。相反,当自定义属性类型错误时,如果属性是可继承的,则该值将从其父级继承。如果属性不可继承,则使用默认的初始值。对于 background-color,属性值不是继承值,因此使用 transparent 的初始值。

为了更好地控制自定义属性的回退方式,可以使用 @property at-rule 来定义属性的初始值。

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

另请参阅