DOM系列:样式和类

发布于 大漠

对于任何一位Web开发者而言,处理CSS样式很多时候还是会借助JavaScript。简单的说,我们会碰到一些交互(或UI效果的变化)都会通过JavaScript来处理style或类。那么今天我们将要学习的是如何通过JavaScript来控制样式和类名,在深入讨论JavaScript处理样式和类的方法之前,我们需要知道在Web页面中元素的样式处理通常有两种方式。

  • 在HTML添加类名,然后在CSS样式文件中处理样式
  • 通过JavaScript控制HTML元素的style属性,改变元素样式

虽然上面提到的方法都可以处理页面元素的样式,但CSS始终是最佳选择 —— 不仅对HTML如此,在JavaScript中也是如此。因为我们一直遵守的是:分离

当然很多时候,只使用CSS的类方式并不一定能达到我们要的结果(也就是说类再也无法处理时),那么我们就必须使用JavaScript操作HTML元素中的style属性。 比如,如果我们要动态计算元素的坐标,并想通过JavaScript来设置,那么我们就可以像下面这样做:

elem.style.left = left
elem.style.top = top

这只是其中的一种情景,事实上还有很多其他的情景,比如将文本的颜色变成红色,比如添加一个背景图片。其实,通过JavaScript来控制CSS类名,更灵活,更容易支持。

至于为什么要这么做,或者要怎么做,接下来的内容就是围绕着这些方面来阐述,感兴趣的,欢迎继续往下阅读。

为什么要使用JavaScript设置样式

文章开头我们提到了可以通过JavaScript来控制Web的样式。那么为什么要使用JavaScript设置元素样式?在深入讨论之前,我们有必要知识为什么要使用JavaScript来设置元素样式。在常见的情况下,咱们可以通过样式文件(就是.css文件或style标签)或内联样式(元素中调用style属性)来控制Web元素的外观。这也可能是大家更多时候想要的。

但在很多时候,是希望内容更具交互性,打个比方说,你希望根据用户输入的状态(比如鼠标单击或者悬浮)引入别的样式,这些样式代码在后台运行。虽然像hover这样的伪选择器提供了一些支持,但是功能还是有很多限制。

言外之意,很多更具交互性(或者说更多的富交互性)的解决方案是需要强度依赖JavaScript。JavaScript不仅可以让你在与之交互的元素上进行样式变化,更重要的是,它允许你在整个页面中使用样式。可以说这种自由性是非常强大的,而且远远超出了CSS对内容样式能力的限制。

两种处理样式的方式

正如文章开头提到的,如果要控制元素的样式变化,有两种方法可能使用JavaScript来修改。一种方法是直接在元素上设置CSS属性(其实就是通过元素内联的style属性来实现)。另一种方法是从元素中添加或删除类名,这可能导致Web运用或Web程序忽略某些样式规则。接下来我们会围绕着这两种方式进行深入的学习。

直接设置style

在学习如何直接修改元素的style(或者如标题写的一样,直接通过设置style)属性来改变元素的样式之前,需要先花点时间来了解CSSStyleDeclaration接口。

CSSStyleDeclaration接口可以用来操作元素的样式。常见的方式(接口)有:

  • 元素节点的style属性,即HTMLElement.style,也就是前面介绍的方式
  • CSSStyle实例的style属性,即HTMLElement.style.cssText
  • window.getComputedStyle()

HTMLElement.style

通过JavaScript访问的每个HTML元素都有一个style对象。该对象允许您指定CSS属性并设置其值。比如你在浏览器控制台中输入$0.style就可以看到类似下图这样的一个截图:

从上图可以看出来,style对象下有的CSS属性。言外之意,上图中的CSS属性(还有很多没截到,可以点击这里查阅读所有的属性)可以通过elem.style.property = 'value'的方式来设置对应属性的值。例如,咱们想对$0设置背景颜色:

$0.style.backgroundColor = '#d93600'

$0事实上对应的就是你在浏览器控制台中选中的元素。

简单地说,要直接使用JavaScript来控制元素样式,第一步是要选中要被修改样式的元素。上面示例使用的是$0这样的快速选择元素方式(在浏览器控制台中可以这么使用),在JavaScript中,可以使用querySelector()方法选中元素,当然在JavaScript中还可以使用其他的方式,比如前面《getElement*querySelector*》一文中介绍选中元素的方法。第二步是找到你关心的CSS属性(想要更换的CSS样式属性,比如上例中的backgroundColor,其实对应的就是CSS中的background-color),然后给该属性赋值。需要注意的是,CSS中的许多值实际上是字符串。也要记住,很多值需要带有像pxem这样的度量单位才能被识别。

通过elem.style.poperty方式修改元素的样式,实际上修改的是HTML元素的style属性的值。比如:

总而言之,可以控制HTML元素的style属性来修改任何你想修改的HTML元素的样式

也就是说,style是HTML元素的一个标签属性,也就是说它也是元素节点。既然是DOM元素的节点,我们就可以通过getAttributesetAttributeremoveAttribute等方法来读写或删除DOM元素的style属性。就上面的示例,咱们可以使用setAttribute()实现同样的效果,比如:

$0.setAttribute(
    'style',
    'background-color: orange; z-index: 9999; border-left-width: 5px;'
)

如果你对getAttributesetAttributeremoveAttribute从未接触或不太了解,建议你花点时间阅读前面整理的文章《AttributeProperty》一文。

上面的示例演示了HTMLElement.style属性来给HTML元素添加样式,其效果等同于使用HTMLElement.setAttribute('style','属性值')。事实上,HTMLElement.style属性返回的是一个CSSStyleDeclaration对象,表示元素的内联style属性,但会忽略样式表应用的属性。

CSSStyleDeclaration接口中的HTMLElement.style接口除了给HTML元素写CSS样式之外,也可以读:

$0.style.backgroundColor    // => "orange"
$0.style.zIndex             // => "9999"
$0.style.borderLeftWidth    // => "5px"

上面代码中,style属性的值是一个CSSStyleDeclaration实例。这个对象所包含的属性与CSS规则一一对应,但CSS属性名需要改写,比如background-color要改成驼峰写法,即backgroundColor。还有一点要特别的注意,如果CSS属性名是JavaScript保留字,则样式名之前需要加上字符串css,比如float要写成cssFloat

HTMLElement.style.cssText

前面的示例也看到了,除了通过setAttribute('style','styleProperty:styleValue')方式给HTML元素的style设置样式之外,还可以通过HTMLElement.style.propery=value的方式来给style属性添加样式。但对于要给一个元素添加多个属性值时,需要多次写,对于追求极速开发体验的码农而言,总感觉会有点蛋疼。在CSSStyleDeclarationHTMLElement.style.cssText属性用来读写当前元素style的值。

先来看读:

$0.style.cssText

输入的值是:

background-color: orange; z-index: 9999; border-left-width: 5px;    

同样的,如果我们要给一个元素添加样式,也可以使用.style.cssText方式,比如:

$0.style.cssText = 'color:#f36; font-size: 2rem;padding: 10px'

这个时候对应的元素的style值变成:

使用HTMLElement.style.cssText给元素的style添加值是要特别注意,如果元素的style属性一开始就有值,使用上面的方式添加值的话将会覆盖原有的属性值。比如:

值得庆幸的是,我们可以使用下面的方式来改变:

$0.style.cssText += 'color:#f36; font-size: 2rem; padding: 10px'

其中colorfont-sizepadding属性被追加到元素的style上,而不是直接覆盖,比如下图所示:

注意,使用cssText的属性值不用改写CSS属性名。

另外,在使用cssText时要给一个元素删除style中的样式,可以给该方法设置值为空字符串,比如:

$0.style.cssText = '';

window.getComputedStyle()

众所周知,HTML元素的行内样式是具有最高的优先级,改变行内样式,通常会立即反映出来。但是,HTML中DOM元素最终的样式是综合各种规则计算出来的。也就是说,要想得到元素实际样式,只读取行内样式是不够的,需要得到浏览器最终计算出来的样式规则。

至于CSS样式是怎么被浏览器计算出来的,真正要理解就要去理解浏览器的工作原理相关的知识了。有关于这部分就不在这里阐述了,如果你感兴趣,可以阅读下面几篇文章:

如果你花时间阅读了上面的文章或者说你对浏览器渲染CSS有一定的了解的话,那么就知道,浏览器解析CSS样式一共有五个来源,其中开发人员只能接触其中的三个来源,HTML元素内联 style属性,HTML的style标签样式和link引用样式;其中另外两个是浏览器客户端的样式:浏览器默认样式和浏览器用户自定义样式。用一张图简单的描述就如下图所示:

其实,如果你平时细心的话,在使用浏览器自带的开发者工具的时候,你就可以查看到浏览器对元素计算出来的属性,比如下图:

而在JavaScript中,咱们就可以使用window.getComputedStyle方法返回浏览器计算后得到的最终样式规则(即计算出来的样式)。这个方法接受一个节点对象作为参数,返回一个CSSStyleDeclaration实例,它包含了指定节点的最终样式信息。比如:

上图可以看到元素$0计算出来的最终样式信息。如果我们把其赋值给一个变量,比如objStyle,那么我们可以通过类似下面的方式取出元素对应的样式属性的值:

objStyle.color              // => rgb(64, 64, 64)
objStyle.backgroundColor    // => rgba(0, 0, 0, 0)

getComputedStyle方法还可以接受第二个参数,表示当前元素的伪元素,比如:before:after:first-line:first-letter等。

window.getComputedStyle($0, ':before')

需要注意的是,节点元素的style对象无法读写伪元素的样式,这个时候就需要用到window.getComputedStyle。使用JavaScript获取伪元素可以用类似下面这样的方法:

window.getComputedStyle($0, ':before').color

除此之外,还可以使用接下来要介绍的CSSStyleDeclaration实例的getPropertyValue方法。比如:

window.getComputedStyle($0, ':before').getPropertyValue('color')

在使用CSSStyleDeclaration实例有几点需要注意:

  • CSSStyleDeclaration实例返回的CSS值都是绝对单位。比如,长度都是像素单位,颜色是rgb()rgba()格式
  • CSS规则的简写形式无效。比如,margin的值不能直接读,需要分开来读,比如marginLeftmarginTop
  • 如果读取CSS原始的属性名,要用方括号运算符,比如objStyle['z-index'],也可以换成驼峰方式objStyle.zIndex
  • 该方法返回的CSSStyleDeclaration实例的cssText属性无效,返回undefined

CSSStyleDeclaration实例的其他方法

CSSStyleDeclaration实例除了上述的一些方法之外,还有其他的一些方法或属性。接下来咱们要学习的就是有关于他的属性和方法。

前面也提到过了,CSSStyleDeclaration实例返回的是一个object,那么我们object的一些属性和方法也可以用到这里。比如lengthitem()之类的等。比如:

let ele = document.querySelector('#viewport')
let objStyle = window.getComputedStyle(ele)
objStyle.length // => 292
objStyle[0]     // => animation-delay

我们也可以通过for循环把其每个属性打印出来,比如:

for (let i = 0; i < objStyle.length; i++) {
    console.log(objStyle[i])
}

除此之外,还有几个实例方法:

  • CSSStyleDeclaration.getPropertyPriority方法接受 CSS 样式的属性名作为参数,返回一个字符串,表示有没有设置important优先级。如果有就返回important,否则返回空字符串。
  • CSSStyleDeclaration.getPropertyValue方法接受 CSS 样式属性名作为参数,返回一个字符串,表示该属性的属性值。
  • CSSStyleDeclaration.removeProperty方法接受一个属性名作为参数,在 CSS 规则里面移除这个属性,返回这个属性原来的值。
  • CSSStyleDeclaration.setProperty方法用来设置新的 CSS 属性。该方法没有返回值。

有关于getComputedStyle更详细的介绍可以阅读:

通过classNameclassList控制样式

在我们Web的开发中,通过控制HTML元素的的类名(添加或删除),从而来控制元素的样式,这也是非常常见的方式。在JavaScript中,可以通过classNameclassList来给元素添加或删除样式,从而实现元素样式的修改。

通过className添加和删除类

通过前面的学习,我们知道className属性允许你访问HTML元素的class属性。通过HTMLElement.className可以读出HTML元素的class属性的值。同样的,给className赋值,可以给元素添加类名,但不同的是,新的类名将会覆盖旧的类名。比如下面这样的一个操作:

可以看到,新添加的new类名,覆盖了已有的jhp类名。如果想让新添加的类名不覆盖已有的类名,可以通过+=的方式来添加,比如:

$0.className += ' jhp'

这个时候重新打印className,其值就是new jhp,在对应的HTML元素相比较而言就成功的添加了新的类名。

不过这样写,有点蛋疼,咱们可以将其封装成一个函数,比如addClass()

function addClass(elements, myClass) {
    // 如果没有元素,我们就跳出函数
    if (!elements) {
        return;
    }

    // 如果有一个选择器,将获取所选择的元素
    if (typeof(elements) === 'string') {
        elements = document.querySelectorAll(elements)
    }

    // 如果我们只有一个DOM元素,那么将它作为一个简化的数组件来操作
    else if (elements.tagName) {
        elements = [elements]
    }

    // 向所有选中的元素添加类
    for (var i = 0; i < elements.length; i++) {
        // 如果类名不存在
        if ((' ' + elements[i].className + ' ').indexOf(' ' + myClass + ' ') < 0) {
            // 添加类
            elements[i].className += ' ' + myClass
        }
    }
}

有了这个函数之后,咱们就可以这样使用了:

addClass('.classdiv','highlight');
addClass(document.getElementById('iddiv'),'highlight');

使用className给指定的元素删除一个类名,相对来说比较麻烦一点。比如上例,咱们使用addClass()函数之后,通过$0.className可以读取出当前元素$0class的值为new jhp primary。如果我们要把类名重新恢复到new jhp时,只能像下面这样:

$0.className = 'new jhp'

为了能更好的操作,咱们也可以类似于addClass()函数一样,写一个removeClass()

function removeClass(elements, myClass) {
    // 如果没有这个元素,直接跳出函数
    if (!elements) {
        return;
    }

    // 如果有一个选择器,将获取所选择的元素
    if (typeof(elements) === 'string') {
        elements = document.querySelectorAll(elements)
    }

    // 如果我们只有一个DOM元素,那么将它作为一个简化的数组件来操作
    else if (elements.tagName) {
        elements = [elements]
    }

    // 创建查找类的模式
    var reg = new RegExp('(^| )' + myClass + '($| )', 'g')

    // 从选中的元素中删除类
    for (var i = 0; i < elements.length; i++) {
        elements[i].className = elements[i].className.replace(reg, ' ')
    }
}

如果要在指定的元素中删除一个类名,就可以像下面这样使用:

removeClass($0, 'new')

最后的结果正如我们所希望的一样:

特别声明,上面addClass()removeClass()两函数的的代码来源于@Yaphi Berhanu写的博文

通过classList来添加和删除类等

上面看的是className给指定元素的class中添加和删除类。事实上,咱们还可以使用classList。在classList中主要的API有:

  • elem.classList.add(): 添加类名
  • elem.classList.remove():删除类名
  • elem.classList.toggle():切换类名,如果改类名存在将会删除该类名,如果不存在将会添加该类名
  • elem.classList.contains:判断是否含有该类名,如果包含则返回true,反之返回的是false

比如我们当前元素$0,可以在浏览器中输出$0.classList对应的值。从返回的结果上可以看出来,其返回的是DOMTokenList。而且从其__proto__中可以找到classList对应的API,比如addcontainsremove等。

DOMTokenList这种类型表示一组空间分隔的标记。通常由HTMLElement.classList, HTMLLinkElement.relList, HTMLAnchorElement.relListHTMLAreaElement.relList返回。从0开始的类JavaScript数组索引。DOMTokenList始终是区分大小写的。

classList.add()

使用classList.add()添加类名,咱们可以像下面这样:

$0.classList.add('make', 'me', 'look', 'rad')

classList.contains()

通过classList.contains()可以判断元素是否包含指定的类名,如果包含返回的是true,如果没包含返回的是false

$0.classList.contains('new')    // => false
$0.classList.contains('make')   // => true

classList.remove()

使用classList.remove()可以删除指定的类名。比如:

classList.toggle()

其实该API就类似一个开关。打个比方说,如果当前元素中有指定的类名,那么执行该 API之后会将指定的类名删除,返之将会添加。类似这样的一个操作:

if ($0.classList.contains('rad')) {
    $0.classList.remove('rad');
} else {
    $0.classList.add('rad');
}

上面的这个功能,就可以使用classList.toggle来替代:

$0.classList.toggle('cool')

使用classList.toggle表示:如果添加类,将返回true,如果删除类,将返回false

let a = $0.classList.toggle('cool');
console.log(a); // => true 

classList.toggle可以选择接受第二个参数,该参数是一个布尔值。可以根据第二个参数来控制是否添加还是删除类。

let someCondition;

let b = shadesEl.classList.toggle('cool', !!someCondition);
console.log(b); // => false, `someCondition` 是 undefined,计算结果是false, class被删除

someCondition = 'I wear my sunglasses at night';

let c = shadesEl.classList.toggle('cool', !!someCondition);
console.log(c); // => true, `someCondition` 计算出来的结果是true, class被添加

有了classList之后,那么前面写的addClass()removeClass()就可以修改一下了。把以前的className替换成现在的classList。如此一来,代码会变得简单的多。比如:

function addClass(elements, myClass) {
    elements = document.querySelectorAll(elements)

    for (var i = 0; i < elements.length; i++) {
        elements[i].classList.add(myClass)
    }
}

function removeClass(elements, myClass) {
    elements = document.querySelectorAll(elements)

    for (var i = 0; i < elements.length; i++) {
        elements[i].classList.remove(myClass)
    }
}
classList其他API

classList除了上面一些常用的API之外,还有lengthvalue属性以及entriesforEachitemkeys等。大家可以根据实际需要的去选择所需要的API。比如:

有关于classList更多的介绍可以阅读:

总结

通过上面的学习,知道如何通过JavaScript来修改CSS样式。我们平常中会使用到的方法会有:

  • 通过DOM Element对象的getAttribute()setAttribute()removeAttribute()等方法修改元素的style属性
  • 通过对元素节点的style来读写行内CSS样式
  • 通过style对象的cssText属性来修改全部的style属性
  • 通过style对象的setProperty()getPropertyValue()removeProperty()等方法来读写行内CSS样式
  • 通过window.getComputedStyle()方法获得浏览器最终计算的样式规则
  • 通过classNameclassList给元素添加或删除类名,配合样式文件来修改元素样式

其实除了上述介绍的方法之外,咱们还可以直接添加样式。常见的方式是通过创建style标签来添加内置的CSS样式表或者通过link标签来引用外部样式表。另外还可以使用addRuleinsertRule来添加样式规则。不过在这篇文章我们没有介绍这些内容,我们将在后续的内容中再来学习这方面的知识。如果你对这方面的知识感兴趣,欢迎持续观注后续的更新。

由于自身是JavaScript的初学者,如果文章中有不对之处,烦请各位拍正。如果您在这方面有更多经验,欢迎在下面的评论中与我们一起分享。如果你觉得这篇文章帮助你了,欢迎给我打赏(^_^)!!!

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/javascript/style-and-class.htmlNike Air Shox Deliver 809 Men Running shoes Deep Blue Black