FrankKai.github.io
FrankKai.github.io copied to clipboard
[译]CSSOM(CSS Object Model)介绍和指南
- 前言
- 什么是CSSOM?
- 通过element.style修改行内样式
- 获得普通元素的计算样式
- 获得伪元素的计算样式
- CSSStyleDeclaration API
- setProperty(),getPropertyValue()和item()
- 使用removeProperty()
- 获取和设置属性的优先级
- CSSStyleSheet Interface
- 与Stylesheet Object一起工作
- 通过CSSOM获取@media规则
- 通过CSSOM获取@keyframes规则
- 增加和删除CSS声明
- 重访CSSStyleDeclaration API
- CSS Typed Object Model...未来?
原文链接:https://css-tricks.com/an-introduction-and-guide-to-the-css-object-model-cssom/
前言
如果你写了一段时间的JS了,大概率是接触过通过js去处理DOM(Document Objectl Model)了。DOM是很多用于操作页面上元素的API。 但其实除了DOM之外,还有一不被常人所知的CSSOM(CSS Object Model)。也可能你已经用过它了但其实并没有注意到。 在这篇文章中,我将列举许多CSSOM中最重要的特性,以一些常见的开始,然后逐渐列举一些不著名的但是实用的特性。
什么是CSSOM?
根据MDN的定义:
CSSOM(CSS Object Model)是一组通过JavaScript修改CSS的API集合。CSSOM和DOM很像,但CSSOM是对于CSS来说的,而不是对于HTML来说的。CSSOM允许用户动态读取和修改CSS的样式。
MDN的解释是基于W3C官方对CSSOM的定义来解释的。W3C文档是一个相当好的去熟悉CSSOM的方式,但是很难从中找到有用的代码片段去调用CSSOM的api。
MDN相对好很多,但是仍然有很多地方是缺失的。 因此在这个博文中,我将尽我的最大努力去创建有用的代码示例和demo,从而可以直观的搞清楚学明白CSSOM的这些api。
文章将首先从对于前端来说很常见的一些api。这些常见的特性常常被混淆成DOM的api,但它们其实是CSSOM。
通过element.style修改行内样式
通过JavaScript最基础的修改和获取CSS属性的方式是style对象,或者property,它对于所有HTML元素都使用。 比如这个例子:
document.body.style.background = 'lightblue'; // 这个颜色好看
大多数人都用过这个语法。可以通过这个语法去添加或者修改页面上任意对象的CSS:
element.style.propertyName
在这个例子中,我将背景色改为了lightblue。当然,background是是缩写。如果想修改background-color属性呢?对于任何有连字符的属性来说,只需将其转为驼峰格式即可:
document.body.style.backgroundColor = 'lightblue';
通常来说,缩写属性直接小写单词就行,对于连字符属性需要通过驼峰获取。 有一个例外,那就是float属性,因为float是js中的一个保留字符串,需要通过cssFloat(或styleFloat <=ie8)。 这一点和HTML中对for属性的获取很像,通过getAttribute()获取属性的话,需要标记它为"htmlFor"。
这里有一个通过CSSOM动态修改body背景色的demo:https://codepen.io/impressivewebs/pen/mQbqGR
通过JavaScript去定义一个CSS属性和它的值是非常简单的。 但是通过这种直接访问style对象的方式有一个很大的限制:只能修改元素的行内样式,换句话说就是必须在标签的style属性上定义。
该怎么去理解这句话呢?
document.body.style.backgroundColor = 'lightblue';
console.log(document.body.style.backgroundColor);
// "lightblue"
上面的例子中,我给body定义了一个行内样式,然后我在控制台打印出了相同的样式。 这是正常的,但是如果我尝试读取元素的另一个属性的话,它什么都不会返回给我们-除非我预先在css中定义或者预先在js中定义过。
console.log(document.body.style.color);
// Returns nothing if inline style doesn't exist
这里有个demo:https://codepen.io/impressivewebs/pen/LXPewe 通过css样式表是无法获取的。
body {
background-color: lightblue;
}
结果为:The current background color is:
但是如果在body标签上为它的style属性有设置,是可以获取的:
<body style="background-color:lightblue">
<p>The current background color is: <output></output></p>
</body>
结果为:The current background color is: lightblue
虽然通过显示设置或者直接用element.style可以设置,但是其实这样是很笨拙的,接下来我们来看一些很有用的通过js去读取和修改css的技术。
在<style></style>
中定义能获取到吗?
不能。
仅适用于在标签style属性上定义的值,style标签上和css文件中都没有用。
在<style></style>
中定义,获取不到。
<!doctype html>
<html>
<head>
<style>
#foo{
color: lightblue;
}
</style>
</head>
<body>
<p id="foo">This is my paragraph.</p>
</body>
<script>
console.log(document.getElementById('foo').style.color); // 什么都不打印
console.log(window.getComputedStyle(document.getElementById('foo')).color); // rgb(173, 216, 230)
</script>
</html>
在p标签上的style属性声明,可以获取到。
<!doctype html>
<html>
<body>
<p id="foo" style="color: lightblue;">This is my paragraph.</p>
</body>
<script>
console.log(document.getElementById('foo').style.color);// lightblue
console.log(window.getComputedStyle(document.getElementById('foo')).color);// rgb(173, 216, 230)
</script>
</html>
获得普通元素的计算样式
可以通过window.getComputedStyle()
方法去读取任意元素的CSS属性。
例如我们上面通过document.body.style.color
无法读取color属性,可以改写为:
window.getComputedStyle(document.body).color;
// rgb(255, 255, 255)
但是对于一些缩写属性来说,通过window.getComputedStyle()查询到的属性比较有趣。
window.getComputedStyle(document.body).background;
"rgb(173, 216, 230) none repeat scroll 0% 0% / auto padding-box border-box"
window.getComputedStyle()查询到的结果,是“过于仁慈的双胞胎”。 如果说element.style给你太少的话,那么window.getComputedStyle(element)就是对你爱的太深,给你太多太多。
这里有一个demo:https://codepen.io/impressivewebs/pen/XyWKJE
这里例子中,background是一个”lightblue“这样的单值,而window.getComputedStyle()给了太多太多,把background缩写属性的所有值都返回了回来。 多余的返回回来的这些默认值,往往是这些css属性的初始值(默认值)。
下面这个例子展示了background、animation、flex通过window.getComputed()获取的结果: https://codepen.io/impressivewebs/pen/OaJXjR
window.getComputed(p).background
// rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box
window.getComputed(p).animation
// none 0s ease 0s 1 normal none running
window.getComputed(p).flex
// 0 1 auto
同样,对于类似width和height的属性,它揭露出元素的计算维度,不管这些值在CSS中有没有明确的定义,就像下面的demo这样:
某种程度上相当于读取window.innerWidth的值,除了这是指定元素的指定属性的计算css,而不仅仅是一个window或者viewport的测量值。
通过window.getComputedStyle(),有几种不同的获取属性的方式。我们已经看了一种通过点语法和驼峰属性名的方式。其实还有另外两种:
// 第一种
window.getComputedStyle(el).backgroundColor
// 第二种
window.getComputedStyle(el)['background-color']
// 第三种
window.getComputedStyle(el).getPropertyValue('background-color')
第二种方式会警告。 建议通过第三种:1.不转换为驼峰获取属性值。 2. 不用转换为cssFloat获取值
获得伪元素的计算样式
有一个知者甚少的关于window.getComputedStyle()的小知识点:可以查询到伪元素的样式。 比如你看到下面这样的代码:
window.getComputedStyle(document.body, null).width;
注意第二个参数null。 Firefox4之前的兼容代码和降级代码会有这样的写法。但是在现代的浏览器中,不再需要这样写了。
第二个可选的参数现在用于声明:我在获取一个伪元素的计算CSS。
看一下下面的代码:
.box::before {
content: 'Example';
display: block;
width: 50px;
}
通过下面的代码,我可以获得.box元素的伪元素的计算样式:
let box = document.querySelector('.box');
window.getComputedStyle(box, '::before').width;
// "50px"
可以参考这个demo: https://codepen.io/impressivewebs/pen/YRXzdm
不仅仅可以获得 ::before,::after这种常见的伪元素,还可以获得类似::first-line这种伪元素的计算样式:
let p = document.querySelector('.box p');
window.getComputedStyle(p, '::first-line').color;
可以参考这个demo:https://codepen.io/impressivewebs/pen/rQVajQ
这里有一个firefox正常其他浏览器出bug的使用::placeholder的方式:
let input = document.querySelector('input');
window.getComputedStyle(input, '::placeholder').color
https://codepen.io/impressivewebs/pen/ZmGGXG
需要注意的是,浏览器在获取不存在的(但是有效的)浏览器不支持的伪元素时,会有不同的表现,比如自定义的伪元素(::banana)
https://codepen.io/impressivewebs/pen/VVLLgx
::banana, firefox不打印,chrome打印出默认值。
firefox有一个getDefaultComputedStyle()方法,不过不是规范的而且很少用到。
CSSStyleDeclaration API
前面我展示了如何通过style对象或者是window.getComputedStyle()去获取属性,它们两个其实都是通过CSSStyleDeclaration interface去暴露出去的。
用其他话说,下面的代码返回的,都是document.body元素的CSSStyleDeclaration对象:
document.body.style;
window.getComputedStyle(document.body);
看下面的截屏,你可以看到:style大多是空的,而window.getComputedStyle()是都有值的。
但是window.getComputedStyle()得到的值,是只读(read-only)的。 element.style得到的值,setter和getter是都有的,但是遗憾的是这些值仅仅影响document的inline style。
setProperty(),getPropertyValue()和item()
如果你通过上面的几种方式之一暴露出了CSSStyleDeclaration对象,会有很多有用的方法去读取和修改这些值。 再强调一遍,getComputedStyle()是只读的,但是通过style属性得到的属性既有setter,也有getter。 考虑以下的代码:
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)}`;
有一个在线demo:https://codepen.io/impressivewebs/pen/vQOKxb
在这个例子中,我们用了3个style的方法:
- setProperty() 用于赋值
- getPropertyValue() 用于取值
- item()用于获取css对应位置的属性值
使用removeProperty()
除了上面的3个setProperty(), getPropertyValue()和item(),CSSStyleDeclaration还暴露了两个其他的方法。 代码和demo中,我将使用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); // ""
通过js移除一个css属性: https://codepen.io/impressivewebs/pen/dQoBZq
获取和设置属性的优先级
还有一个很有趣的方法,叫做getPropertyPriority()。
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'); // ""
https://codepen.io/impressivewebs/pen/EOjqKd
box.style.setProperty('font-family', 'Georgia, serif', 'important');
设置了!important,可以设置成空或者空字符串去没有important优先级。
如果我有以下的代码:
<div class="box" style="border: solid 1px red !important;">
会为所有border缩写相关的属性返回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'));
CSSStyleSheet Interface
inline样式不常用,computed style常用但是会有困扰。
还有一个很好好用的查询样式表的api:CSSStyleSheet。 从document的样式表查询信息的最简方式是通过styleSheets属性。
可以这样查看当前页面有多少样式表:
document.styleSheets.length; // 1
可以通过这样去得到样式表的引用:
document.styleSheets[0];
cssRules是最有用的属性。 cssRules中包括了block声明,at-rule和media rules等等。 在后面的章节中,我将详细介绍如何通过stylesheet object去读取和修改样式。
与Stylesheet Object一起工作
* {
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 += `<code>${i.selectorText}</code><br>`;
}
}
https://codepen.io/impressivewebs/pen/VVemNb
The selector text for each style rule in the stylesheet is:
*
body
main
.component
a:hover
code
type为1的意思是:STYLE _RULE。 其他的type类型分别为:IMPORT_RULE(3), MEDIA_RULE(4),KEYFRAMES_RULE(7)等等。
seletcorText是规则关联的选择器。它是可写的属性。 如果我想修改a:hover为a:hover, a:active,可以这样写:
if (i.selectorText === 'a:hover') {
i.selectorText = 'a:hover, a:active';
}
https://codepen.io/impressivewebs/pen/oQbYKZ
通过CSSOM获取@media规则
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 += `<code>${j.keyText}</code><br>`;
}
}
}
https://codepen.io/impressivewebs/pen/MzyeWd
还可以查到媒体查询的查询条件:conditionText/mediaText
let myRules = document.styleSheets[0].cssRules,
p = document.querySelector('.output');
for (i of myRules) {
if (i.type === 4) {
p.innerHTML += `<code>${i.conditionText}</code><br>`;
// (max-width: 800px)
}
}
https://codepen.io/impressivewebs/pen/OaNXgo
通过CSSOM获取@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 += `<code>${j.keyText}</code><br>`;
}
}
}
https://codepen.io/impressivewebs/pen/MzybxL
"0%"
"20%"
"100%"
from和to代表的是0%和100%。
// 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;
还可以查到keyframe的name:
let myRules = document.styleSheets[0].cssRules,
p = document.querySelector('.output');
for (i of myRules) {
if (i.type === 7) {
p.innerHTML += `<code>${i.name}</code><br>`;
}
}
https://codepen.io/impressivewebs/pen/oQxWGg
可以打印出帧动画的颜色:
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 += `<code>${j.style.color}</code><br>`;
}
}
}
https://codepen.io/impressivewebs/pen/jQqmXp
增加和删除CSS声明
可以通过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
https://codepen.io/impressivewebs/pen/QJNMgN
insertRule的第二个参数是可选的,用来标记插入的位置,默认从头部0位置插入,类似数组的unshift操作。
let myStylesheet = document.styleSheets[0];
console.log(myStylesheet.cssRules.length); // 8
myStylesheet.deleteRule(3);
console.log(myStylesheet.cssRules.length); // 7
https://codepen.io/impressivewebs/pen/OaNjxL
重访CSSStyleDeclaration API
<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
document.styleSheets[0].cssRules[0].style
// 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');
https://codepen.io/impressivewebs/pen/aQZvXB
CSS Typed Object Model...未来?
CSS Typed OM
- bug更少
- 算术转换和单位转换
- 更好的性能
- 异常处理
- CSS属性名只允许字符串