CSS 对象模型 (CSSOM) 入门指南

Avatar of Louis Lazaris
Louis Lazaris 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

如果您已经编写 JavaScript 一段时间了,那么您几乎肯定编写了一些处理文档对象模型 (DOM) 的脚本。DOM 脚本利用了网页打开一组 API(或接口)的事实,因此您可以操作和处理页面上的元素。

但是,您可能还想熟悉另一个对象模型:CSS 对象模型 (CSSOM)。您可能已经使用过它,但没有意识到。

在本指南中,我将介绍 CSSOM 的许多最重要的功能,首先介绍更常见的功能,然后介绍一些更晦涩但实用的功能。

什么是 CSSOM?

根据 MDN:

CSS 对象模型是一组 API,允许从 JavaScript 操作 CSS。它非常类似于 DOM,但用于 CSS 而不是 HTML。它允许用户动态读取和修改 CSS 样式。

MDN 的信息基于 官方 W3C CSSOM 规范。该 W3C 文档是了解 CSSOM 的可能性的一个相当不错的方式,但对于任何希望找到一些将 CSSOM API 应用于实践的实用编码示例的人来说,它都是一场灾难。

MDN 更好,但在某些领域仍然存在很大不足。因此,对于这篇文章,我尽力创建了这些接口使用的实用代码示例和演示,以便您可以看到可能性并使用实时代码进行操作。

如前所述,这篇文章从大多数前端开发人员已经熟悉的内容开始。这些常见功能通常与 DOM 脚本一起使用,但它们在技术上属于通过 CSSOM 提供的更大接口组的一部分(尽管它们也与 DOM 重叠)。

通过 element.style 使用内联样式

使用 JavaScript 操作或访问 CSS 属性和值的最基本方法是通过 style 对象或属性,该对象或属性在所有 HTML 元素上都可用。这是一个示例

document.body.style.background = 'lightblue';

你们中的大多数人可能以前见过或使用过这种语法。我可以使用相同的格式向页面上的任何对象添加或更改 CSS:element.style.propertyName

在这个例子中,我将 background 属性的值更改为 lightblue。当然,background 是简写。如果我想更改 background-color 属性怎么办?对于任何带连字符的属性,只需将属性名称转换为驼峰式大小写即可

document.body.style.backgroundColor = 'lightblue';

在大多数情况下,单个单词的属性将以小写形式使用等效的单个单词来访问,而带连字符的属性则以驼峰式大小写表示。唯一的例外是使用 float 属性时。因为 float 是 JavaScript 中的保留字,所以您需要使用 cssFloat(如果您支持 IE8 及更早版本,则使用 styleFloat)。这类似于在使用 getAttribute() 等时,HTML for 属性被引用为 htmlFor

这是一个使用 style 属性允许用户更改当前页面背景颜色的演示

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 使用 style 对象更改背景颜色

因此,这是一种使用 JavaScript 定义 CSS 属性和值的方法。但是,以这种方式使用 style 属性有一个巨大的警告:**这仅适用于元素上的内联样式**。

当您使用 style 属性读取 CSS 时,这一点变得很清楚

document.body.style.backgroundColor = 'lightblue';
console.log(document.body.style.backgroundColor);
// "lightblue"

在上面的示例中,我在 <body> 元素上定义了一个内联样式,然后我将相同的样式记录到控制台。这很好。但是,如果我尝试读取该元素上的另一个属性,它将返回空值——除非我之前在我的 CSS 或 JavaScript 中的其他地方为该元素定义了内联样式。例如

console.log(document.body.style.color);
// Returns nothing if inline style doesn't exist

即使在外部样式表中定义了 <body> 元素上的 color 属性,这也会返回空值,如下面的 CodePen 所示

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 element.style 仅读取内联样式

使用 element.style 是通过 JavaScript 向元素添加样式的最简单和最常见的方法。但正如您所看到的,这显然有一些明显的限制,因此让我们看看一些更实用的读取和操作样式的技术。

获取计算样式

您可以使用 window.getComputedStyle() 方法读取元素上任何 CSS 属性的计算 CSS 值

window.getComputedStyle(document.body).background;
// "rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box"

好吧,这是一个有趣的结果。在某种程度上,window.getComputedStyle()style 属性过于仁慈的孪生兄弟。虽然 style 属性为您提供了关于元素实际样式的信息太少,但 window.getComputedStyle() 有时会为您提供过多的信息。

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 getComputedStyle() 方法可以读取任何 CSS 属性

在上面的示例中,<body> 元素的 background 属性是使用单个值定义的。但是,getComputedStyle() 方法返回包含在 background 简写中的所有值。在 CSS 中未明确定义的值将返回这些属性的初始(或默认)值。

这意味着,对于任何简写属性,window.getComputedStyle() 将返回所有初始值,即使 CSS 中没有定义任何一个值

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 window.getComputedStyle() 返回简写属性的所有长格式值

类似地,对于 widthheight 等属性,它将显示元素的计算尺寸,无论这些值是否在 CSS 中的任何位置明确定义,如下面的交互式演示所示

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 即使未在 CSS 中定义,window.getComputedStyle() 也返回宽度和高度值

尝试调整上面演示中父元素的大小以查看结果。这有点类似于读取 window.innerWidth 的值,只是这是指定元素上指定属性的计算 CSS,而不仅仅是常规的窗口或视口测量。

有几种不同的方法可以使用 window.getComputedStyle() 访问属性。我已经演示了一种方法,该方法使用点表示法将驼峰式大小写的属性名称添加到方法的末尾。您可以在下面的代码中看到三种不同的方法来做到这一点

// dot notation, same as above
window.getComputedStyle(el).backgroundColor;

// square bracket notation
window.getComputedStyle(el)['background-color'];

// using getPropertyValue()
window.getComputedStyle(el).getPropertyValue('background-color');

第一行使用与先前演示中相同的格式。第二行使用方括号表示法,这是 JavaScript 中点表示法的常见替代方法。不建议使用此格式,并且代码 linter 会发出警告。第三个示例使用 getPropertyValue() 方法。

第一个示例需要使用驼峰式大小写(尽管在这种情况下 floatcssFloat 都可以使用),而接下来的两个示例通过与 CSS 中使用的语法相同的语法(使用连字符,通常称为“短横线式大小写”)访问属性。

这与之前的演示相同,但这次使用 getPropertyValue() 访问两个元素的宽度

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 将 window.getComputedStyle() 与 getPropertyValue() 一起使用

获取伪元素的计算样式

关于 window.getComputedStyle() 的一个鲜为人知的小细节是,它允许您检索伪元素的样式信息。您经常会看到这样的 window.getComputedStyle() 声明

window.getComputedStyle(document.body, null).width;

请注意传递给方法的第二个参数 null。Firefox 4 之前的版本需要第二个参数,这就是为什么您可能在遗留代码或习惯于包含它的代码中看到它的原因。但它在当前使用的任何浏览器中都不是必需的。

第二个可选参数允许我指定我正在访问伪元素的计算 CSS。考虑以下 CSS

.box::before {
  content: 'Example';
  display: block;
  width: 50px;
}

在这里,我在 .box 元素内添加了一个 ::before 伪元素。使用以下 JavaScript,我可以访问该伪元素的计算样式

let box = document.querySelector('.box');
window.getComputedStyle(box, '::before').width;
// "50px"

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 使用 getComputedStyle() 获取伪元素的样式

您也可以对其他伪元素(如 ::first-line)执行此操作,如下面的代码和演示所示

let p = document.querySelector('.box p');
window.getComputedStyle(p, '::first-line').color;

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 使用 getComputedStyle() 获取伪元素的样式

这是一个使用 ::placeholder 伪元素的示例,该元素应用于 <input> 元素

let input = document.querySelector('input');
window.getComputedStyle(input, '::placeholder').color

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 使用 getComputedStyle() 获取 ::placeholder 伪元素的样式

以上代码在最新版本的 Firefox 中有效,但在 Chrome 或 Edge 中无效(我已经为 Chrome 提交了错误报告)。

还需要注意的是,浏览器在尝试访问不存在(但有效)的伪元素的样式与完全不支持的伪元素(例如虚构的 ::banana 伪元素)时,会产生不同的结果。您可以使用以下演示在各种浏览器中尝试:

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的笔 测试不存在的伪元素上的 getComputedStyle()

作为本节的补充说明,Firefox 独有的一种方法叫做 getDefaultComputedStyle(),它不是规范的一部分,并且可能永远也不会成为规范的一部分。

CSSStyleDeclaration API

之前我向您展示了如何通过 style 对象或使用 getComputedStyle() 来访问属性,在这两种情况下,这些技术都公开了 CSSStyleDeclaration 接口。

换句话说,以下两行代码都将在文档的 body 元素上返回一个 CSSStyleDeclaration 对象

document.body.style;
window.getComputedStyle(document.body);

在下图中,您可以看到控制台为这两行代码分别输出的内容

The CSSStyleDeclaration API in the DevTools console

对于 getComputedStyle(),这些值是只读的。对于 element.style,可以获取和设置值,但如前所述,**这些更改只会影响文档的内联样式**。

setProperty()、getPropertyValue() 和 item()

一旦您通过上述方法之一公开了 CSSStyleDeclaration 对象,就可以访问许多有用的方法来读取或操作这些值。同样,对于 getComputedStyle(),这些值是只读的,但当通过 style 属性使用时,一些方法可用于获取和设置。

请考虑以下代码和演示

let box = document.querySelector('.box');

box.style.setProperty('color', 'orange');
box.style.setProperty('font-family', 'Georgia, serif');
op.innerHTML = box.style.getPropertyValue('color');
op2.innerHTML = `${box.style.item(0)}, ${box.style.item(1)}`;

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的笔 使用 CSSStyleDeclaration API 的三种不同方法

在这个示例中,我使用了 style 对象的三种不同方法

  • setProperty() 方法。它接受两个参数,每个参数都是一个字符串:属性(使用常规 CSS 表示法)和您希望分配给该属性的值。
  • getPropertyValue() 方法。它接受一个参数:您想要获取其值的属性。此方法在前面的示例中使用过 getComputedStyle(),如前所述,它也公开了 CSSStyleDeclaration 对象。
  • item() 方法。它接受一个参数,该参数是一个正整数,表示您要访问的属性的索引。返回值是该索引处的属性名称。

请记住,在我的上述简单示例中,元素的内联 CSS 中只添加了两个样式。这意味着,如果我访问 item(2),返回值将为空字符串。如果我使用 getPropertyValue() 访问该元素的内联样式中未设置的属性,也会得到相同的结果。

使用 removeProperty()

除了上面提到的三种方法之外,CSSStyleDeclaration 对象上还公开了另外两种方法。在以下代码和演示中,我使用了 removeProperty() 方法

box.style.setProperty('font-size', '1.5em');
box.style.item(0) // "font-size"

document.body.style.removeProperty('font-size');
document.body.style.item(0); // ""

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的笔 使用 CSSSTyleDeclaration API 的 removeProperty() 方法

在这种情况下,在使用 setProperty() 设置 font-size 后,我会记录属性名称以确保它存在。然后,演示包含一个按钮,单击该按钮将使用 removeProperty() 删除该属性。

对于 setProperty()removeProperty(),您传入的属性名称使用连字符分隔(与样式表中的格式相同),而不是驼峰命名法。这起初可能看起来令人困惑,但此示例中传入的值是字符串,因此这是有道理的。

获取和设置属性的优先级

最后,这是我在研究本文时发现的一个有趣的功能:getPropertyPriority() 方法,以下面的代码和 CodePen 演示。

box.style.setProperty('font-family', 'Georgia, serif', 'important');
box.style.setProperty('font-size', '1.5em');

box.style.getPropertyPriority('font-family'); // important
op2.innerHTML = box.style.getPropertyPriority('font-size'); // ""

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的笔 使用 getPropertyPriority() 获取属性的“重要性”

在该代码的第一行,您可以看到我正在使用 setProperty() 方法,就像我之前做的那样。但是,请注意,我已经包含了第三个参数。第三个参数是一个可选字符串,用于定义您是否希望将 !important 关键字附加到该属性。

在使用 !important 设置属性后,我使用 getPropertyPriority() 方法检查该属性的优先级。如果希望属性不具有重要性,您可以省略第三个参数,使用关键字 undefined,或将第三个参数包含为空字符串。

并且我应该在这里强调,这些方法将与已直接放置在元素 style 属性的 HTML 中的任何内联样式一起使用。

所以如果我有以下 HTML

<div class="box" style="border: solid 1px red !important;">

我可以使用本节中讨论的任何方法来读取或以其他方式操作该样式。并且应该在这里注意,由于我为此内联样式使用了简写属性并将其设置为 !important,因此构成该简写的所有详细属性在使用 getPropertyPriority() 时都将返回 important 的优先级。请参见下面的代码和演示

// These all return "important"
box.style.getPropertyPriority('border'));
box.style.getPropertyPriority('border-top-width'));
box.style.getPropertyPriority('border-bottom-width'));
box.style.getPropertyPriority('border-color'));
box.style.getPropertyPriority('border-style'));

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的笔 使用 getPropertyPriority() 检查详细属性的优先级

在演示中,即使我仅在 style 属性中显式设置了 border 属性,构成 border 的所有关联详细属性也将返回 important 的值。

CSSStyleSheet 接口

到目前为止,我所考虑的大部分内容都涉及内联样式(通常不太有用)和计算样式(很有用,但通常过于具体)。

一个更有用的 API 允许您检索具有可读和可写值的样式表,而不仅仅是内联样式,那就是 CSSStyleSheet API。访问文档样式表信息的最简单方法是使用当前文档的 styleSheets 属性。这将公开 CSSStyleSheet 接口。

例如,以下代码行使用 length 属性查看当前文档有多少个样式表

document.styleSheets.length; // 1

我可以使用基于零的索引引用文档中的任何样式表

document.styleSheets[0];

如果我将该样式表记录到我的控制台,就可以查看可用的方法和属性

The CSSStyleSheet Interface in the DevTools Console

其中一个将被证明有用的属性是 cssRules 属性。此属性提供该样式表中包含的所有 CSS 规则(包括声明块、@规则、媒体规则等)的列表。在以下各节中,我将详细介绍如何利用此 API 来操作和读取外部样式表中的样式。

使用样式表对象

为了简单起见,让我们使用一个只有少量规则的示例样式表。这将使我能够演示如何使用 CSSOM 以类似于通过 DOM 脚本访问元素的方式访问样式表的不同部分。

这是我将要使用的样式表

* {
  box-sizing: border-box;
}

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 2em;
  line-height: 1.4;
}

main {
  width: 1024px;
  margin: 0 auto !important;
}

.component {
  float: right;
  border-left: solid 1px #444;
  margin-left: 20px;
}

@media (max-width: 800px) {
  body {
    line-height: 1.2;
  }

  .component {
    float: none;
    margin: 0;
  }
}

a:hover {
  color: lightgreen;
}

@keyframes exampleAnimation {
  from {
    color: blue;
  }
  
  20% {
    color: orange;
  }
  
  to {
    color: green;
  }
}

code {
  color: firebrick;
}

我可以使用此示例样式表尝试许多不同的操作,我将在此处演示其中的一些。首先,我将遍历样式表中的所有样式规则,并记录每个规则的选择器文本

let myRules = document.styleSheets[0].cssRules,
    p = document.querySelector('p');

for (i of myRules) {
  if (i.type === 1) {
    p.innerHTML += `<c​ode>${i.selectorText}</c​ode><br>`;
  }
}

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的笔 使用 CSSStyleSheet – 记录选择器文本

在上面的代码和演示中需要注意几点。首先,我缓存了样式表的 cssRules 对象的引用。然后我循环遍历该对象中的所有规则,检查每个规则的类型。

在这种情况下,我想要类型为 1 的规则,它表示 STYLE_RULE 常量。其他常量包括 IMPORT_RULE (3)、MEDIA_RULE (4)、KEYFRAMES_RULE (7) 等。您可以在 这篇文章中查看这些常量的完整列表。

当确认一条规则是样式规则时,我会打印每个样式规则的 `selectorText` 属性。这将为指定的样式表生成以下行

*
body
main
.component
a:hover
code

`selectorText` 属性是用于该规则的选择器的字符串表示形式。这是一个可写属性,因此如果需要,可以在原始的 `for` 循环中更改特定规则的选择器,代码如下所示:

if (i.selectorText === 'a:hover') {
  i.selectorText = 'a:hover, a:active';
}

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 操作选择器文本

在此示例中,我正在寻找一个为链接定义 `:hover` 样式的选择器,并将其扩展到也应用于 `:active` 状态下的元素。或者,可以使用某种字符串方法甚至正则表达式来查找所有 `:hover` 实例,然后在此基础上进行操作。但这足以演示其工作原理。

使用 CSSOM 访问 @media 规则

你会注意到我的样式表还包含一个媒体查询规则和一个关键帧 at-rule 块。当我搜索样式规则(类型 1)时,这两个都被跳过了。现在让我们找到所有 `@media` 规则

let myRules = document.styleSheets[0].cssRules,
    p = document.querySelector('.output');

for (i of myRules) {
  if (i.type === 4) {
    for (j of i.cssRules) {
      p.innerHTML += `<c​ode>${j.selectorText}</c​ode><br>`;
    }
  }
}

根据给定的样式表,以上代码将生成以下输出:

body
.component

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 访问 @media 规则

如你所见,在循环遍历所有规则以查看是否存在任何 `@media` 规则(类型 4)后,我再循环遍历每个媒体规则的 `cssRules` 对象(在本例中只有一个),并记录该媒体规则中每个规则的选择器文本。

因此,在 `@media` 规则上公开的接口类似于在样式表上公开的接口。但是,`@media` 规则还包含一个 `conditionText` 属性,如下面的代码片段和演示所示:

let myRules = document.styleSheets[0].cssRules,
    p = document.querySelector('.output');

for (i of myRules) {
  if (i.type === 4) {
    p.innerHTML += `<c​ode>${i.conditionText}</c​ode><br>`;
    // (max-width: 800px) 
  }
}

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 访问 @media 规则

此代码循环遍历所有媒体查询规则,并记录确定该规则何时适用的文本(即条件)。还有一个 `mediaText` 属性返回相同的值。根据规范,你可以获取或设置这两个属性中的任何一个。

使用 CSSOM 访问 @keyframes 规则

既然我已经演示了如何从 `@media` 规则中读取信息,那么让我们考虑如何访问 `@keyframes` 规则。以下是一些入门代码:

let myRules = document.styleSheets[0].cssRules,
    p = document.querySelector('.output');

for (i of myRules) {
  if (i.type === 7) {
    for (j of i.cssRules) {
     p.innerHTML += `<c​ode>${j.keyText}</c​ode><br>`;
    }
  }
}

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 访问 @keyframes 规则

在此示例中,我正在寻找类型为 7(即 `@keyframes` 规则)的规则。找到一个后,我循环遍历该规则的所有 `cssRules` 并记录每个规则的 `keyText` 属性。在这种情况下,日志将是:

"0%"
"20%"
"100%"

你会注意到我的原始 CSS 使用 `from` 和 `to` 作为第一个和最后一个关键帧,但 `keyText` 属性将其计算为 `0%` 和 `100%`。 `keyText` 的值也可以设置。在我的示例样式表中,可以像这样硬编码:

// Read the current value (0%)
document.styleSheets[0].cssRules[6].cssRules[0].keyText;

// Change the value to 10%
document.styleSheets[0].cssRules[6].cssRules[0].keyText = '10%'

// Read the new value (10%)
document.styleSheets[0].cssRules[6].cssRules[0].keyText;

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 设置 @keyframes 规则

使用此方法,可以动态更改 Web 应用程序流程中的动画关键帧,或者可能响应用户操作。

在访问 `@keyframes` 规则时,另一个可用的属性是 `name`

let myRules = document.styleSheets[0].cssRules,
    p = document.querySelector('.output');

for (i of myRules) {
  if (i.type === 7) {
    p.innerHTML += `<c​ode>${i.name}</c​ode><br>`;
  }
}

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 获取 @keyframes 规则的名称

回想一下,在 CSS 中,`@keyframes` 规则如下所示:

@keyframes exampleAnimation {
  from {
    color: blue;
  }
  
  20% {
    color: orange;
  }
  
  to {
    color: green;
  }
}

因此,`name` 属性允许我读取为该 `@keyframes` 规则选择的自定义名称。这是在特定元素上启用动画时将在 `animation-name` 属性中使用的相同名称。

最后要提的一件事是能够获取单个关键帧内的特定样式。以下是一些示例代码和演示:

let myRules = document.styleSheets[0].cssRules,
    p = document.querySelector('.output');

for (i of myRules) {
  if (i.type === 7) {
    for (j of i.cssRules) {
      p.innerHTML += `<c​ode>${j.style.color}</c​ode><br>`;
    }
  }
}

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 访问 @keyframes 规则内的属性值

在此示例中,在找到 `@keyframes` 规则后,我循环遍历关键帧中的每个规则(例如,“from”规则、“20%”规则等)。然后,在每个规则中,我访问单个 `style` 属性。在本例中,由于我知道 `color` 是为每个规则定义的唯一属性,因此我只是记录颜色值。

此处的重点是使用 `style` 属性或对象。之前我展示了如何使用此属性访问内联样式。但在这种情况下,我使用它来访问单个关键帧内的各个属性。

你可能已经看到了这将带来哪些可能性。这允许你在运行时修改单个关键帧的属性,这可能是由于某些用户操作或应用程序或基于 Web 的游戏中发生的某些其他事件导致的。

添加和删除 CSS 声明

`CSSStyleSheet` 接口可以访问两个方法,允许你添加或删除样式表中的整个规则。这两个方法是: `insertRule()` 和 `deleteRule()`。让我们看看它们如何在操作示例样式表中发挥作用。

let myStylesheet = document.styleSheets[0];
console.log(myStylesheet.cssRules.length); // 8

document.styleSheets[0].insertRule('article { line-height: 1.5; font-size: 1.5em; }', myStylesheet.cssRules.length);
console.log(document.styleSheets[0].cssRules.length); // 9

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 插入规则

在本例中,我记录了 `cssRules` 属性的长度(显示样式表最初包含 8 条规则),然后使用 `insertRule()` 方法将以下 CSS 作为单个规则添加:

article {
  line-height: 1.5;
  font-size: 1.5em;
}

我再次记录 `cssRules` 属性的长度,以确认规则已添加。

`insertRule()` 方法将字符串作为第一个参数(这是必需的),包含要插入的完整样式规则(包括选择器、花括号等)。如果要插入 at-rule,则可以在此字符串中包含完整的 at-rule,包括嵌套在 at-rule 内部的各个规则。

第二个参数是可选的。这是一个整数,表示要插入规则的位置或索引。如果未包含此参数,则默认为 `0`(表示规则将插入规则集合的开头)。如果索引大于规则对象的长度,则会抛出错误。

`deleteRule()` 方法的使用要简单得多

let myStylesheet = document.styleSheets[0];
console.log(myStylesheet.cssRules.length); // 8

myStylesheet.deleteRule(3);
console.log(myStylesheet.cssRules.length); // 7

查看 CodePen 上 Louis Lazaris (@impressivewebs) 的示例:使用 CSSStyleSheet API 删除规则

在本例中,该方法接受一个参数,表示要删除的规则的索引。

对于这两种方法,由于基于零的索引,作为参数传入的选定索引必须小于 `cssRules` 对象的长度,否则会抛出错误。

重新访问 CSSStyleDeclaration API

之前我解释了如何访问声明为内联样式的各个属性和值。这是通过 `element.style` 完成的,它公开了 `CSSStyleDeclaration` 接口。

但是,`CSSStyleDeclaration` API 也可以作为 `CSSStyleSheet` API 的子集公开在单个样式规则上。当演示如何访问 `@keyframes` 规则内的属性时,我已经提到了这一点。要了解其工作原理,请比较以下两个代码片段:

<div style="color: lightblue; width: 100px; font-size: 1.3em !important;"></div>
.box {
  color: lightblue;
  width: 100px;
  font-size: 1.3em !important;
}

第一个示例是一组内联样式,可以按如下方式访问:

document.querySelector('div').style

这公开了 `CSSStyleDeclaration` API,它允许我执行诸如 `element.style.color`、`element.style.width` 等操作。

但是,可以在外部样式表中的单个样式规则上公开完全相同的 API。这意味着我将 `style` 属性的使用与 `CSSStyleSheet` 接口结合使用。

因此,上面第二个示例中的 CSS 使用与内联版本完全相同的样式,可以像这样访问:

document.styleSheets[0].cssRules[0].style

这在样式表中的一条样式规则上打开了一个单独的 `CSSStyleDeclaration` 对象。如果有多个样式规则,则可以使用 `cssRules[1]`、`cssRules[2]`、`cssRules[3]` 等访问每个规则。

因此,在一个外部样式表中,在一个类型为1的单个样式规则内,我可以访问前面提到的所有方法和属性。这包括setProperty()getPropertyValue()item()removeProperty()getPropertyPriority()。此外,这些相同的特性在@keyframes@media规则内的单个样式规则上也是可用的。

以下是一个代码片段和演示,展示了如何在我们的示例样式表中的单个样式规则上使用这些方法。

// Grab the style rules for the body and main elements
let myBodyRule = document.styleSheets[0].cssRules[1].style,
    myMainRule = document.styleSheets[0].cssRules[2].style;

// Set the bg color on the body
myBodyRule.setProperty('background-color', 'peachpuff');

// Get the font size of the body
myBodyRule.getPropertyValue('font-size');

// Get the 5th item in the body's style rule
myBodyRule.item(5);

// Log the current length of the body style rule (8)
myBodyRule.length;

// Remove the line height
myBodyRule.removeProperty('line-height');

// log the length again (7)
myBodyRule.length;

// Check priority of font-family (empty string)
myBodyRule.getPropertyPriority('font-family');

// Check priority of margin in the "main" style rule (!important)
myMainRule.getPropertyPriority('margin');

查看 CodePen 上 Louis Lazaris (@impressivewebs) 编写的 在外部样式表中操作单个样式规则的样式对象

CSS 类型对象模型……未来?

在我考虑了本文中的所有内容之后,如果我不得不透露,有一天我们所知的 CSSOM 可能大部分都会过时,这似乎很奇怪。

这是因为一个名为CSS 类型 OM的东西,它是Houdini 项目的一部分。尽管一些人指出新的类型 OM 与当前的 CSSOM 相比更加冗长,但正如Eric Bidelman 在这篇文章中概述的那样,其优势包括

  • 更少的错误
  • 算术运算和单位转换
  • 更好的性能
  • 错误处理
  • CSS 属性名称始终为字符串

有关这些特性的完整详细信息以及语法概览,请务必查看完整文章

截至撰写本文时,CSS 类型 OM 仅在 Chrome 中受支持。您可以在此文档中查看浏览器支持的进度。

结语

通过 JavaScript 操作样式表当然不是每个项目都会做的事情。并且,此处介绍的方法和属性所实现的一些复杂交互具有一些非常具体的用例。

如果您构建了某种使用任何这些 API 的工具,我很想了解它。我的研究仅触及了可能的表面,但我希望看到如何在现实世界的示例中使用任何这些内容。

我已将本文中的所有演示放入一个 CodePen 集合中,因此您可以随意随意使用它们。