原文链接:https://javascript.info/styles-and-classes,translate with ❤️ by zhangbao.

在正式进入话题之前,还要再啰嗦一下。一般给一个 HTML 元素设置样式有两种方式:

  1. 创建一个类名,通过添加类名来控制样式:<div class="...">

  2. 直接写在 style 特性中,也就是内联样式:<div style="...">

使用类名通常来说是更好的方式——不仅对 HTML,对 JavaScript 操作来说也是一样。

我们仅在类名“不能处理它”的情况下,在使用 style 属性。

例如,在需要使用 JavaScript 动态变动元素坐标的时候,通过操作 style 属性来实现效果就是可接受的。

  1. let top = /* 复杂计算 */
  2. let left = /* 复杂计算 */
  3. elem.style.left = left; // 比如 '123px'
  4. elem.style.top = top; // 比如 '456px'

对于其他情况,例如将文本设置为红色,添加背景图标——使用类名控制样式的方式会更加灵活,也更容易支持。

className 和 classList

在 JavaScript 代码里操作元素类名是最常发生的场景之一。

很久之前,“class”是 Javascript 里的一个保留字,不能作为对象属性使用,所有元素的 class 特性对应的属性采用了“className”而不是“class”,现在限制不存在了,但是为了兼容,又不能改回去了。所以,最终还是用 elem.className 获取 class 特性了。

例如:

  1. <body class="main page">
  2. <script>
  3. alert(document.body.className); // main page
  4. </script>
  5. </body>

直接给 elem.className 赋值会整个替换掉 class 特性值的,有时候符合我们的要求,但通常我们希望的是只操作其中的一个类名。这时候,elem.classList 就派上用场了。

elem.classList 是一个特殊的对象,提供了 add/remove/toggle 等操作类名的方法。

例如:

  1. <body class="main page">
  2. // 添加类名
  3. document.body.classList.add('article');
  4. alert(document.body.className); // main page article
  5. </div>

可以看到,使用 classList 我们能够操作单个类名。

下面列出了 classList 上暴露的方法:

  • elem.classList.add/remove('class'):添加/删除类名。

  • elem.classList.toggle('class'): 无则添加,有则删除。

  • elem.classList.contains('class'):检查是有包含某个类名,返回 true/false

除此之外,classList 是可迭代对象,因此我们可以这样列举出所有类名:

  1. <body class="main page">
  2. <script>
  3. for (let name of document.body.classList) {
  4. alert(name); // main 然后是 page
  5. }
  6. </script>
  7. </body>

元素样式

HTML 的 style 特性对应的 elem.style 属性是一个对象。设置 elem.style.width="100px" 相当于设置了 style="width:100px"

对于多单词属性,操作对应的属性时,使用驼峰形式的命名:

  1. background-color => elem.style.backgroundColor
  2. z-index => elem.style.zIndex
  3. border-left-width => elem.style.borderLeftWidth

例如:

  1. document.body.style.backgroundCOlor = prompt('背景色是?', '绿色');

操作样式和类名 - 图1前缀属性

-moz-border-radius-webkit-border-radius 这些浏览器前缀睡醒发,也遵循这样的规则。例如:

  1. button.style.MozBorderRadius = '50px'
  2. button.style.WebkitBorderRadius = '50px'

也就是把 - 之后的字符大写,然后去掉 -

重置样式

有时,我们先要添加 style 样式,然后又要在不久删掉它。

例如,隐藏一个元素,我们可以设置 elem.style.display = 'none'

之后,我们想删除 style.display,就像之前没有设置一样。不是用 delele elem.style.display 的方式,我们直接把对应属性设置为空就行了:elem.style.display = ''

  1. document.body.style.display = 'none'; // 隐藏
  2. setTimeout(() => document.body.style.display = '', 1000); // 1秒后,恢复显示

操作样式和类名 - 图2
操作样式和类名 - 图3

看到没有,属性设置为空后,在 HTML 渲染结果里就看不到了,自然而然的,就使用默认样式了。

操作样式和类名 - 图4使用 style.cssText`` 是完全覆盖

通常我们使用 style.*的方式设置单独的属性样式,但不能这样做:elem.style="color: red; width: 100px",因为 elem.style`` 是个只读对象。

完全重写 style特性的话,使用 elem. style.cssText 属性。

```html

按钮
``` > > 我们很少使用这个属性,因为它是会重写整个 style`` 特性,而不是附加、替换它们。当然,如果我们是在新元素上使用的话,就不用担心什么覆盖问题了。 > > 还记得 elem.setAttribute(prop, value)`` 方法吗?我们使用 elem.setAttribute('style', 'color: red...')`` 的方式,也能达到跟 elem.style.cssText = 'color: red...'`` 一样的效果。 ## 注意单位 在操作 `elem.style` 对象,设置样式的时候,一定要记得带上单位。 例如,如果我们设置 `elem.style.top` 值为 `10` 就不起作用,而应该设置成 `10px` 才行。 ```html ``` 在最后一行我们看到,`style.margin` 被解构了,所以 `marginTop` 和 `marginLeft` (当然还有 `marginRight` 和 `marginBottom`)的值是能访问到的。 ## 计算属性:getComputedStyle 修改样式容易,那么怎么读呢? 例如,我们想知道在没有设置任何 CSS 样式时,元素的大小、`margin` 或者颜色是什么? `style` 属性操作的是 `style` 特性本身,没有 CSS 层级。 因此,我们不能使用 `style` 读取任何类名应用的 CSS 样式。 ```html 红色文本 ``` 但是如果我们需要在现有 `margin` 的值上,再额外增加 `20px` 怎么办呢? 可以使用另一个方法:`getComputedStyle`。 语法是: ```javascript getComputedStyle(element[, pseudo]) ``` **element** 从中读取样式的元素。 **pseudo** 伪元素,比如 `::before`,是可选项。不赋值或者使用空字符串,表示获取 `elem` 元素本身的样式。 例如: ```html

  1. > ![](https://cdn.nlark.com/yuque/0/2018/png/103346/1538095037530-6d778d01-90bc-4f48-908c-e204955103d7.png#width=26)**计算值和解析值**
  2. >
  3. > [CSS](https://drafts.csswg.org/cssom/#resolved-values) 中有两个概念:
  4. >
  5. > 1. _计算_属性值是最终叠加到元素身上的样式,也就是最后应用的样式,看起来像 `height: 1em` 或者 `font-size: 125%`
  6. > 2. _解析_属性值元素最终使用的样式。像 `1em` 或者 `125%` 都是相对值。浏览器会根据这个相对值,解析出最终的固定单位(比如 `px`)的值。例如,`height: 20px` 或者 `font-size: 16px`。对于几何属性,解析值可能是一个浮点数。
  7. >
  8. > 很久以前,`getComputedStyle` 创造出来时,是用来获取计算属性值的,但后来发现解析值可能更方便些,然后标准就改了。
  9. >
  10. > 所以现在 `getComputedStyle` 方法,实际上返回的是属性解析值。
  11. > ![](https://cdn.nlark.com/yuque/0/2018/png/103346/1538096580119-8835ab2e-ed83-4e3f-a505-55407d09775f.png#width=28)**`getComputedStyle` 需要完整的属性名称**
  12. >
  13. > 我们总是应该使用 `getComptedStyle` 方法获取具体属性值,像 `paddingLeft` 或者 `marginTop` 或者 `borderTopWidth`。否则的话,可能不会得到一个正确的值。
  14. >
  15. > 例如,我们给元素设置了属性 `paddingLeft/paddingTop`,那么我们使用 `getComputedStyle(elem).padding` 会得到什么结果呢?这个地方,标准里并没有说明。所以,在这点上,不同浏览器上的表现出现了不一致。
  16. >
  17. > 例如:下面这个例子里,有些浏览器打印 `10px`,有些浏览器没有打印结果。
  18. >
  19. > ```html
  20. <style>
  21. body {
  22. margin: 10px;
  23. }
  24. </style>
  25. <script>
  26. let style = getComputedStyle(document.body);
  27. alert(style.margin); // 在 Chrome 里打印结果是 10px,在 Firefox 中的打印结果是空字符串
  28. </script>

操作样式和类名 - 图5:visited** 伪类在 ****getComputedStyle** 中会被隐藏

已访问状态的链接可能会使用 CSS 伪类 :visited 赋予样式。

但 getComputedStyle`` 不允许访问该颜色,因为否则任意页面可以通过在页面上创建链接并检查样式来查明用户是否访问了链接。

使用 JavaScript 我们可能看不到应用的 :visited样式。而且,CSS 的一个限制是禁止在 :visited 伪类上使用几何变化样式。这是为了保证邪恶的页面没有侧面来测试链接是否被访问并因此破坏了隐私。

总结

管理类名,可以使用两种 DOM 属性:

  • className:字符串值,用于操作整个 class 特性。

  • classList:一个包含 add/remove/toggle/contains 方法的对象,适合用来管理单个类名。

修改样式:

  • style 属性是一个对象,包含可以设置各个特性的、驼峰形式的属性。我们可以通过它控制元素的 style 特性,相关信息在 MDN 上能看到。

  • style.cssText 属性用来设置整个 style 特性,给它赋值会覆盖原来 style 中的值。

读取解析样式(元素应用的最终样式):

  • getComputed(elem[, pseudo]) 返回类样式对象,是只读的。

练习题

问题

一、创建一个通知框

写一个函数 showNotification(options) 用来创建一个通知框:<div class="notification">,使用我们给定的内容,在 1.5 秒之后,通知框会自动消失。

传入的选项对象包括:

  1. // 使用文本“你好”来窗口的右上角附近创建一个元素
  2. showNotification({
  3. top: 10, // 距离窗口顶部 10px (默认是 0px)
  4. right: 10, // 距离窗口右边缘 10px (默认是 0px)
  5. html: "你好!", // 通知内容的 HTML
  6. className: "welcome" // 为 div 添加类名 (可选)
  7. });

使用 CSS 定位以给定的 top/right 坐标显示元素。源文档具有必要的样式。

答案

一、

  1. function showNotification({top = 0, right = 0, className, html}) {
  2. let notification = document.createElement('div');
  3. notification.className = 'notification';
  4. if (className) {
  5. notification.classList.add(className);
  6. }
  7. notification.style.top = top + 'px';
  8. notification.style.right = right + 'px';
  9. notification.innerHTML = html;
  10. document.body.append(notification);
  11. setTimeout(() => notification.remove(), 1500);
  12. }

(完)