一:前言
在 web 程序中,经常需要动态的控制 css 样式来丰富我们的页面。在如今这个框架横行的年代,这些原生的样式操作方式,你知道多少?
二:Element.className
Element.className
:一个DOMString
,表示这个元素的 class。多个 class 之间使用空格进行分割。
如果我们有一个元素的 outerHTML
是 <div class="demo active red"></div>
,那么该元素的 className
值为 'demo active red'
。
如果我们使用这种方式进行元素类进行管理,其实并不是一种好的方式,因为这里面涉及到大量字符串的切割、拼接、删除操作。那么接下来我将使用 class 的 add
、remove
和 replace
三种操作方式简单演示一下:
/**
* 移除 class
* @param elem - 需要进行 class 操作的元素
* @param token - 需要被移除的 class 名
*/
function removeClass(elem: Element, ...tokens: string[]) {
const temp = elem.className.split(/\s+/).filter((token) => !tokens.includes(token))
elem.className = temp.join(' ')
}
/**
* 添加 class
* @param elem - 需要进行 class 操作的元素
* @param token - 需要被添加的 class 名
*/
function addClass(elem: Element, ...tokens: string[]) {
const temp = new Set([...elem.className.split(/\s+/), ...tokens])
elem.className = [...temp].join(' ')
}
/**
* 替换 class
* @param elem - 需要进行 class 操作的元素
* @param oldToken - 被替换的 class 名
* @param newToken - 替换的 class 名
*/
function replaceClass(elem: Element, oldToken: string, newToken: string) {
const names = elem.className.split(/\s+/).filter((token) => token !== oldToken)
names.push(newToken)
elem.className = names.join(' ')
}
当然,Element.className = 'xxx xxx'
的操作方式与 Element.setAttribute('class', 'xxx xxx')
是等效的,同理 const className = Element.className
与 const className = Element.getAttribute('class')
等效。
三:Element.classList
Element.classList
:返回该元素包含的 class 属性,是一个DOMTokenList
。
在操作 class 方面,classList
是最简单高效的方式了,因为它内部封装有丰富的 API 来供我们进行 class 操作。
同样是拿 add
、remove
和 replace
三种操作方式举例:
// <div class="demo active red"></div>
const elem = document.querySelector('.demo')
// addClass
elem.classList.add('bgcolor') // <div class="demo active red bgcolor"></div>
// removeClass
elem.classList.remove('demo') // <div class="active red bgcolor"></div>
// replaceClass
elem.classList.replace('active', 'focus') // <div class="focus red bgcolor"></div>
看了这番操作是不是瞬间觉得很简单,除了这三个方法之外,classList
还有丰富的 API 供我们使用,详情请查看 MDN
。
四:HTMLElement.style
HTMLElement.style
:获取/设置元素的style属性,是一个CSSStyleDeclaration
。
直到现在,是不是还有同学在用 getAttribute
和 setAttribute
进行 style 的管理?如果是的,那意味着你做了很多的无用功,就像有了 classList
你还在用 className
进行 class 管理一样。
getter
/setter
首先我们来看一看比较常规的 HTMLElement.style
用法:
// <div style="height: 30px;background: aqua;color: #FF0000 !important;" id="demo">文字</div>
const elem = document.querySelector('#demo')
// getter
const color = elem.style.color
const bgcolor = elem.style.backgroundColor // 以 “-” 分割的样式名被转为 “小驼峰”
// setter
elem.style.color = '#FFFF00' // <div style="height: 30px;background: aqua;color: #FFFF00;" id="demo">文字</div>
// remove
elem.style.height = null // <div style="background: aqua;color: #FFFF00;" id="demo">文字</div>
elem.style.color = '' // <div style="background: aqua;" id="demo">文字</div>
这种 getter/setter
的运用方式已经能满足我们绝大多数的场景了,但是如果你想对某个样式使用 !important
进行修饰的时候,你会发现 setter
直接失效了:
遇到这种特殊的情况,你是否会觉得我们又要回到 setAttribute
的管理方式来进行处理了?其实大可不必,CSSStyleDeclaration
给我们提供了相应的 API 来处理各种需求。
getPropertyValue
/setProperty
/removeProperty
// <div style="height: 30px;background: aqua;color: #FF0000;" id="demo">文字</div>
const elem = document.querySelector('#demo')
// getter
const color = elem.style.getPropertyValue('color')
const bgcolor = elem.style.getPropertyValue('background-color')
// setter
elem.style.setProperty('color', '#FFF000') // <div style="height: 30px;background: aqua;color: #FFF000;" id="demo">文字</div>
elem.style.setProperty('color', '#FFFF00', 'important') // <div style="height: 30px;background: aqua;color: #FFFF00 !important;" id="demo">文字</div>
// remove
elem.style.removeProperty('color') // <div style="height: 30px;background: aqua;" id="demo">文字</div>
elem.style.setProperty('height', null) // <div style="background: aqua;" id="demo">文字</div>
elem.style.setProperty('background', '') // <div style="" id="demo">文字</div>
五:HTMLStyleElement.sheet
HTMLStyleElement.sheet:即
<style>.sheet
,是一个CSSStyleSheet
或null
。
HTMLStyleElement
其实就是我们经常使用的 <style>
标签,用 <style>
动态控制 css 样式?
你可能会有 “<style>
不是在开发过程中使用的吗?怎么能实现动态控制 css 的呢?” 这样的疑问,以为我搞错了。我们这里是使用 <style>.sheet
来动态控制 css 样式的,而非通过添加/移除 <style>
标签来动态控制 css 样式,区别还是很明显的。
null
<style>.sheet
的返回值可能是 null
,那什么时候返回 null
呢?根据我的使用经验(没查到相关文档),在我们创建了一个 <style>
实例(const style = document.createElement('style')
),但我们还未将这个实例插入 DOM
树(document.head.appendChild(style)
)的这个阶段,<style>.sheet
的返回值是 null
。
那在这个阶段如果我们要动态的控制 css 样式,我们就只能进行原始字符串操作了,但我们需要注意的是,这里只能使用 <style>.textContent
进行 css 样式的书写,而不能使用 <style>.innerText
。
如果我们使用 <style>.innerText
进行赋值操作,那么浏览器会将我们赋值的内容按照 html
的方式进行解析。当我们的 css 字符串未进行压缩的状态下,最终结果便是样式无效!
CSSStyleSheet
当 <style>.sheet
的返回值是 CSSStyleSheet
时,此时我们仍然可以使用 <style>.textContent
对 css 样式的更改,但是这样是没有必要的,因为我们有更便捷的管理方式!
修改样式规则
要对某个 <style>
下已存在的样式进行修改,我们需要先了解 CSSStyleSheet.cssRules
和 CSSStyleRule
。
CSSStyleSheet.cssRules
:返回一个实时的CSSRuleList
,其中包含组成样式表的CSSRule
对象的一个最新列表。
但实际上 CSSRuleList
是由 CSSStyleRule
构成的实时样式列表,CSSRule
只是一个接口,而 CSSStyleRule
则是对 CSSRule
接口的具体实现。
CSSStyleRule
:表示一条 CSS 样式规则。它实现了CSSRule
接口。
那我们先来看一下 CSSStyleSheet.cssRules
到底长什么样?
从图上我们可以看到,CSSStyleRule.style
和 HTMLElement.style
一样,都是一个 CSSStyleDeclaration
对象,都讲到这里了还不知道怎么操作,那就往上翻,回顾一遍。
备注:更改
<style>.textContent
会刷新CSSStyleSheet.cssRules
,反之则不会!
添加/删除的样式规则
添加:CSSStyleSheet.insertRule(cssText[, index])
:向样式表的特定位置插入一条新规则,需要提供新规则的完整文本。
删除:CSSStyleSheet.deleteRule(index)
:从样式表中删除特定位置的一条规则。
/*
<style type="text/css" id="demo">
body {
margin: 0;
padding: 0;
background-color: pink;
}
.box {
display: inline-block;
width: 200px;
height: 200px;
background-color: red;
}
</style>
*/
const style = document.querySelector('#demo')
const sheet = style.sheet as CSSStyleSheet
// 添加样式规则
sheet.insertRule('.box { border: 1px solid #999; }') // 添加到末尾
sheet.insertRule('.box { padding: 10px 15px 5px; }', sheet.cssRules.length) // 添加到末尾
// 删除样式规则
sheet.deleteRule(sheet.cssRules.lenght - 1) // 删除末尾一条规则
备注:
cssText
是一条完整的 css 规则,包含选择器和样式声明。index
的取值范围值0 ≤ index ≤ <style>.sheet.cssRules.length
。
对
HTMLStyleElement.sheet
感兴趣的同学,可以看看interim.ts
这个文件。
六:结语
除了 HTMLStyleElement.sheet
的这种骚操作不常见之外,前面的几种大家或多或少都用到过,只是可能用得没这么细。那么 HTMLStyleElement.sheet
的操作如此之骚,有没有什么应用场景呢?其实是有的,比如做富文本编辑器的选区,做富文本编辑器“查找替换”功能的高亮显示这些。
个人感觉,文章中的这些操作除了做原生开发,基本都用不到了!
注:作者在写这篇文章的过程中,MDN
网站的部分页面突然打不开了,这也致使文章中的某些关键字未添加相关链接,添加的某些链接可能打不开的情况......,见谅