目录

🥗 JavaScript DOM 样式和类

JavaScript 修改样式一般通过 修改 DOM 元素的 style 属性。

🌰 例子 / 动态计算元素的坐标:

let top = /* 复杂的计算 */;
let left = /* 复杂的计算 */;

elem.style.left = left; // 例如 '123px',在运行时计算出的
elem.style.top = top; // 例如 '456px'
1
2
3
4
5

# class...

elem.className 对应于 class 特性(attribute), 字符串值,可以很好地管理整个类的集合。。

🌰 例子:

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

特殊对象 classList类列表),提供 add/remove/toggle 单个类的方法:

  • elem.classList.add/remove(class) :添加 / 移除类。
  • elem.classList.toggle(class) :如果类不存在就添加类,存在就移除它。
  • elem.classList.contains(class) :检查给定类,返回 true/false

🌰 例子:

<body class="main page">
  <script>
    // 添加一个 class
    document.body.classList.add('article');

    alert(document.body.className); // main page article
  </script>
</body>
1
2
3
4
5
6
7
8

# 元素样式 style

element.style 属性是一个对象,它对应于 style 特性中所写的内容。

遵循 驼峰式命名,意味着在特性中使用 - 的多词命名, - 意味着大写字母,在属性中使用驼峰式命名,例如 background-color 对应 element.style.backgroundColor

🌰 例子:

document.body.style.backgroundColor = 'green'
1

-moz-border-radius-webkit-border-radius 这样的浏览器前缀属性,也同样遵循这个规则。

# 重置样式

  • 隐藏一个 元素,可以设置 element.style.display = 'none'
  • 移除这个样式,将这个样式的属性值设置为空: element.style.display = '' 。这时元素重新出现。

(空字符串的样式属性,浏览器会应用 CSS 类以及内建样式,就像这个属性没有出现过)

# 完整重写样式

通常使用 element.style.* 对各个样式赋值,但是不能设置完整的样式 style ,因为整个 div.style 是一个只读对象。

想要以字符串的形式设置完整的样式,可以使用特殊属性 style.cssText

🌰 例子:

<div id="div">Button</div>
1
div.style.cssText = `color: red !important 
		background-color: yellow;
    width: 100px;
    text-align: center;`;

console.log(div.css.cssText)
1
2
3
4
5
6

这样的方式对元素的样式赋值,会删除现有的元素样式;因为它不是添加而是替换。所以这个方法不安全。

要想在原来的基础上添加,设置一个新的特性实现同样的效果:

div.setAttribute('style', 'color: red; ... ')
1

# 注意样式单位

🌰 例子 / 设置 top 样式:

<body>
  <script>
    // 无效!
    document.body.style.margin = 20;
    alert(document.body.style.margin); // ''(空字符串,赋值被忽略了)

    // 现在添加了 CSS 单位(px)— 生效了
    document.body.style.margin = '20px';
    console.log(document.body.style.margin); // 20px

    console.log(document.body.style.marginTop); // 20px
    console.log(document.body.style.marginLeft); // 20px
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 计算样式 getComputedStyle

修改样式可以直接对样式对应的属性赋值;例如直接 element.body.style.color 不能获取到样式属性的值。

要获取样式属性的值,可以使用:

let computedStyle = getComputedStyle(element, [presudo])
1
  • element :要被读取样式值的元素;
  • [presudo] :伪元素;例如 ::before 。空字符串或者无参数意味着 元素本身

获取到的结果是一个具有 样式属性的只读对象。类似 element.style

🌰 例子:

<head>
  <style> body { color: red; margin: 5px } </style>
</head>
<body>

  <script>
    let computedStyle = getComputedStyle(document.body);

    // 现在我们可以读取它的 margin 和 color 了

    alert( computedStyle.marginTop ); // 5px
    alert( computedStyle.color ); // rgb(255, 0, 0)
  </script>

</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

对于 marginpadding 类型的样式,最好使用精准的完整属性名,例如 paddingLeftmarginTopborderTopWidth 。否则,就不能保证正确的结果。

getComputedStyle 实际上返回的事属性的 解析值

  • 计算 (computed) 样式值是所有 CSS 规则和 CSS 继承都应用后的值,这是 CSS 级联(cascade)的结果。例如 height:1emfont-size:125%
  • 解析 (resolved) 样式值是最终应用于元素的样式值值。诸如 1em125% 这样的值是相对的。浏览器将使用计算(computed)值,并使所有单位均为固定的,且为绝对单位,例如: height:20pxfont-size:16px 。对于几何属性,解析(resolved)值可能具有浮点,例如: width:50.5px

# 元素大小

在 JavaScript 中有许多属性可让我们读取有关元素宽度、高度和其他几何特征的信息。

🌰 例子:

.example 元素中有以下的样式:

<div id="example">
  ...Text...
</div>
<style>
  #example {
    width: 300px;
    height: 200px;
    border: 25px solid #E8C48F;
    padding: 20px;
    overflow: auto;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12

注意 滚动条的占用宽度空间:一些浏览器(并非全部)通过从 内容(content width) 获取空间为 滚动条保留空间。如果没有滚动条,内容宽度将是 300 px ,但是如果滚动条宽度是 16px (不同的设备和浏览器,滚动条的宽度可能有所不同),那么最后的内容宽度还剩下 300 - 16 = 284px

最近的浏览器版本已经将 滚动条的宽度 不再占用 内容的宽度了。

# 几何属性

# offsetParent

元素最外面的几何属性:

  • offsetParent :是最接近的祖先元素,在浏览器渲染期间,它被用于计算坐标

    • 最近的祖先为:CSS 中定位为 position: absolute / relative / fixed ;或者 <td> / <th> / <table> ;或者 <body>
  • offsetLeft / offsetTop 提供相对于 offsetParent 左上角的 x / y 坐标

🌰 例子:

<main style="position: relative" id="main">
  <article>
    <div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
  </article>
</main>

<script>
  alert(example.offsetParent.id); // main
  alert(example.offsetLeft); // 180
  alert(example.offsetTop); // 180
</script>
1
2
3
4
5
6
7
8
9
10
11

注意获取到的是不带单位的 数字(不是字符串 180px )。

对于几种情况, offsetParent 可能为 null :

  • 未显示的元素( display:none 或者不在文档中)。
  • 对于 <body><html> 。(无祖先元素)
  • 对于带有 position:fixed 的元素。

# offsetWidth/Height

在元素内部(本身中),这两个属性提供了 元素「外部」的宽度和高度(包括边框的完整大小)

🌰 例子:

这个例子中:

  • offsetWidth390 (外部宽度):

    25px + 20px + 300px + 20px + 25px (不包含滚动条宽度)

  • offsetHeight290 (外部高度):

    同理。

# client*

元素内部中有 边框 时,为了测量他们(边框的宽度)可以使用 client* (相对来说,是内部和外部的相对坐标):

同上例子:

  • clientTop = 25 :上边框宽度
  • clientLeft = 25 :左边框宽度

# clientWidth/Height

计算元素边框内区域的大小。包括 内容宽度(content width)和 padding 。(不包括滚动条宽度)

同上例子:

  • clientHeight = 200 :为内部的元素高度( 200px )加上 padding 的宽度( 2 * 20px
  • clientWidth = 340 内部元素宽度( 300px ) 加上 padding 的宽度 ( 2 * 20px ) 。

# scrollWidth/Height

这个属性是 完整的 clientWidth/Height :它们包括滚动出的隐藏部分。

  • scrollWidth = 340
  • scroll = *

利用这个属性可以将属性展开到整个 宽度或者高度。

🌰 例子:

# scrollLeft/scrollTop

这两个属性是元素的 隐藏 / 滚动部分的 width / Height ,换句话说, scrollTop 就是 “已经滚动了多少”。

大多数几何属性是只读的,但是 scrollLeft/scrollTop 是可修改的,并且浏览器会滚动该元素。

🌰 例子:点击下面的元素,则会执行代码 elem.scrollTop += 10 。这使得元素内容向下滚动 10px

🌰 例子 / 将元素滚动到顶部或者底部:

element.scrollTop = 0 // 顶部
element.scrollTop = 1e9 // 底部
1
2

# CSS 样式属性与集合属性比较

  • getComputedStyle 可以获取 CSS 样式中 元素的 widthheight

    let element = document.body
    console.log(getComputedStyle(element).width)
    
    1
    2
  • 但是应该使用 几何属性 获得宽度、高度和计算距离:

    • CSS width / height 取决于另一个属性: box-sizing ,它定义了 CSS 宽度和高度。出于 CSS 的目的而对 box-sizing 进行的更改可能会破坏此类 JavaScript 操作。

    • CSS 的 width/height 可能是 auto ,例如内联(inline)元素:

      <span id="elem">Hello!</span>
      
      <script>
        console.log( getComputedStyle(elem).width ); // auto
      </script>
      
      1
      2
      3
      4
      5

      从 CSS 的观点来看, width:auto 是完全正常的,但在 JavaScript 中,需要一个确切的 px 大小,以便在计算中使用它。

    • 滚动条占用元素宽度的原因,导致内容的实际宽度小于 CSS 宽度。

    • 使用 getComputedStyle(element).width 获取的值是一个字符串。而几何元素是 数值。

# 窗口大小

# 获取窗口的 width / height

利用几何元素:使用 document.documentElementclientWidth / clientHeight

关于 window.innerWidth / window.innerHeight

注意

对比使用 client* ,窗口的宽度有所不同。因为此时 滚动条占用了宽度的一些空间。而 client* 会减去滚动条的宽度。所以使用 client* 返回的是可见文档内容的 width / heiught

在大多数情况下,需要通过 可见窗口的宽度 绘制或者放置元素,所以应该使用更加合适的 client*

# 文档大小

# 获取文档的 width / height

根文档元素是 document.documentElement ,并且它包围了所有内容,因此可以通过使用 documentElement.scrollWidth / scrollHeight 来测量文档的完整大小。

由于未明历史原因, body / document.Elementhtml 根元素)获取出来的 高度和宽度可能有所不同,所以要获取可靠的数值,要取最大值:

# 滚动状态

# 获得当前滚动位置

相对于当前页面:

::: demo[vanilla]

<html>
  <button onclick="alert('当前已从顶部滚动:' + window.pageYOffset)">当前已从顶部滚动</button>
  <button onclick="alert('当前已从左侧滚动:' + window.pageXOffset)">当前已从左侧滚动</button>
</html>
1
2
3
4

:::

也可以从 window.scrollX / window.scrollY 中获取相同的信息(两种属性相同)。

# 控制滚动位置

注意

注意要在 页面 DOM 完全构建后 才能通过 JavaScript 滚动页面。所以脚本不能放在 <head> 标签当中。

通过更改 scrollTop / scrollLeft 滚动元素;

使用 document.documentElement.scrollTop/scrollLeft 对页面进行相同的操作(Safari 除外,而应该使用 document.body.scrollTop/Left 代替)。

或者使用更加简单的特殊方法:

  • window.scrollBy(x, y) :将页面滚动至 相对于当前位置的 (x, y) 位置。如, scrollBy(0,10) 会将页面向下滚动 10px

🌰 例子:

  • window.scrollTo(pageX, pageY) :将页面滚动至 绝对坐标,使得可见部分的左上角具有相对于文档左上角的坐标。 (pageX, pageY) 。就像给 scrollLeft / scrollTop 设值一样。

🌰 例子:

(这些方法适用于所有浏览器)

  • element.scrollIntoView(top) :将滚动页面以使 element 可见。它有一个参数:
    • 如果 top = true (默认值),页面滚动,使 element 出现在窗口顶部。元素的上边缘将与窗口顶部对齐
    • 如果 top = false ,页面滚动,使 element 出现在窗口底部。元素的底部边缘将与窗口底部对齐

# 禁止滚动

要使文档不可滚动,只需要设置 document.body.style.overflow = "hidden" 。该页面将 “冻结” 在其当前滚动位置上。

取消只要重置该属性的值即可。

# 坐标

JavaScript 两个坐标系:

  • 相对于 窗口:类似于 position:fixed ,从窗口的顶部 / 左侧边缘计算得出。** 表示为 clientX / clientY ** ;

  • 相对于 文档:与文档根中的 position:absolute 类似,从文档的顶部 / 左侧边缘计算得出。表示为 pageX/pageY

# 获取元素坐标

使用方法 element.getBoundingClientRect() 返回 最小矩形 的窗口坐标,该矩形将 element 作为内建的 DOMRect 类的对象。

DOMRect 类的对象包含主要的属性有:

  • x/y :矩形原点相对于窗口的 X/Y 坐标;
  • width/height :矩形的 width/height(可以为负);
  • top/bottom :顶部 / 底部矩形边缘的 Y 坐标;
  • left/right : 左 / 右矩形边缘的 X 坐标。

🌰 例子 / 获取按钮元素的 窗口坐标:

<html>
  <input id="brTest" type="button" value="Get coordinates using button.getBoundingClientRect() for this button" onclick="showRect(this)">
</html>
<script>
function showRect(elem) {
  let r = elem.getBoundingClientRect();
  alert(`x:${r.x}
         y:${r.y}
         width:${r.width}
         height:${r.height}
         top:${r.top}
         bottom:${r.bottom}
         left:${r.left}
         right:${r.right}
        `);
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

随着窗口相对按钮位置的改变,其窗口坐标(如果你垂直滚动页面,则为 y/top/bottom )也随之改变。

可以得出:

  • left = x
  • top = y
  • right = x + width
  • bottom = y + height

提示

注意:

  • 坐标可能是小数;浏览器内部使用小数进行计算,所以设置元素的数值不一定必须舍入。
  • 坐标可能是负数。例如滚动页面,元素在页面上方时。

从数学上讲,一个矩形是使用其起点 (x,y) 和方向向量 (width,height) 唯一定义的。因此,其它派生属性是为了方便起见。

width/height 可能为负数,负数时从左上方构建 矩形。此时 left / right 不等于 x / y (此时 x/y 在右下角)

但是实际上, elem.getBoundingClientRect() 总是返回正数的 width/height 。看起来重复了的属性,其实不重复的。

注意

此处的 right / bottom 要与 CSS 中的定位区分:

  • 在 CSS 定位中, right 属性表示距右边缘的距离,而 bottom 属性表示距下边缘的距离。

# elementFromPoint(x,y)

document.elementFromPoint(x, y) 的调用会返回在窗口坐标 (x, y) 处嵌套最多的元素。

let element = document.elementFromPoint(x, y)
1

🌰 例子 / 高亮显示并输出现在位于窗口中间的元素的标签:

let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;

let elem = document.elementFromPoint(centerX, centerY);
elem.style.background = "red";
console.log(element.tagName)
1
2
3
4
5
6

注意

对于在窗口之外的坐标, elementFromPoint 返回 null 。所以只对在可见区域内的坐标 (x,y) 起作用。

# 结合 CSS 定位

使用 getBoundingClientRect 来获取其坐标,然后使用 CSS position 以及 left/top (或 right/bottom )。

🌰 例子 / 函数 createMessageUnder(elem, html)elem 下显示了消息:

let elem = document.getElementById("coords-show-mark");

function createMessageUnder(elem, html) {
  // 创建 message 元素
  let message = document.createElement('div');
  // 在这里最好使用 CSS class 来定义样式
  message.style.cssText = "position:fixed; color: red";

  // 分配坐标,不要忘记 "px"!
  let coords = elem.getBoundingClientRect();

  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}

let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

上面的例子 message 元素依赖于 position:fixed ,因此当页面滚动时,它仍位于窗口的同一位置。

如果要使跟随元素移动,要使用 position: relative 以及使用基于文档的坐标。

# 文档坐标

文档相对坐标从文档的左上角开始计算。(与顶部的 position:absolute 类似)

获取正确的文档坐标,利用 client* :

  • pageY = clientY + 文档的垂直滚动出的部分的高度。
  • pageX = clientX + 文档的水平滚动出的部分的宽度。

🌰 例子 / 将从 elem.getBoundingClientRect() 获取元素相对于窗口坐标,并向其中添加当前滚动:

function getCoords(elem) {
  let box = elem.getBoundingClientRect();

  return {
    top: box.top + window.pageYOffset,
    right: box.right + window.pageXOffset,
    bottom: box.bottom + window.pageYOffset,
    left: box.left + window.pageXOffset
  };
}
1
2
3
4
5
6
7
8
9
10

结合上面的消息例子:

function createMessageUnder(elem, html) {
  let message = document.createElement('div');
  message.style.cssText = "position:absolute; color: red";

  let coords = getCoords(elem);

  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

此时页面滚动时,消息仍停留在元素附近。

📢 上次更新: 2022/09/02, 10:18:16