这些原生的样式操作方式,你,知道多少?

352

一:前言

在 web 程序中,经常需要动态的控制 css 样式来丰富我们的页面。在如今这个框架横行的年代,这些原生的样式操作方式,你知道多少?

二:Element.className

Element.className:一个 DOMString,表示这个元素的 class。多个 class 之间使用空格进行分割。

如果我们有一个元素的 outerHTML<div class="demo active red"></div>,那么该元素的 className 值为 'demo active red'

如果我们使用这种方式进行元素类进行管理,其实并不是一种好的方式,因为这里面涉及到大量字符串的切割、拼接、删除操作。那么接下来我将使用 class 的 addremovereplace 三种操作方式简单演示一下:

/**
 * 移除 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.classNameconst className = Element.getAttribute('class') 等效。

三:Element.classList

Element.classList:返回该元素包含的 class 属性,是一个 DOMTokenList

在操作 class 方面,classList 是最简单高效的方式了,因为它内部封装有丰富的 API 来供我们进行 class 操作。

同样是拿 addremovereplace 三种操作方式举例:

// <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

直到现在,是不是还有同学在用 getAttributesetAttribute 进行 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 直接失效了:

image.png

遇到这种特殊的情况,你是否会觉得我们又要回到 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,是一个 CSSStyleSheetnull

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.cssRulesCSSStyleRule

CSSStyleSheet.cssRules:返回一个实时的 CSSRuleList,其中包含组成样式表的 CSSRule 对象的一个最新列表。

但实际上 CSSRuleList 是由 CSSStyleRule 构成的实时样式列表,CSSRule 只是一个接口,而 CSSStyleRule 则是对 CSSRule 接口的具体实现。

CSSStyleRule:表示一条 CSS 样式规则。它实现了 CSSRule 接口。

那我们先来看一下 CSSStyleSheet.cssRules 到底长什么样?

image.png

从图上我们可以看到,CSSStyleRule.styleHTMLElement.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 网站的部分页面突然打不开了,这也致使文章中的某些关键字未添加相关链接,添加的某些链接可能打不开的情况......,见谅