Skip to content

元素大小和滚动

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

我们在 JavaScript 中移动或定位元素时,我们会经常需要它们。

示例元素

作为演示属性的示例元素,我们将使用下面给出的元素:

<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

它有边框(border),内边距(padding)和滚动(scrolling)等全套功能。但没有外边距(margin),因为它们不是元素本身的一部分,并且它们没什么特殊的属性。

这个元素看起来就像这样:

你可以 在 sandbox 中打开这个文档代码。

<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <style type="text/css">
    * {
      margin: 0;
      padding: 0;
    }

    #example {
      width: 300px;
      height: 200px;
      overflow: auto;
      border: 25px solid #E8C48F;
      padding: 20px;
    }

    .key {
      cursor: pointer;
      text-decoration: underline;
    }
  </style>

</head>

<body>


  <div id="example">
    <h3>Introduction</h3>
    <p>This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company's Navigator 2.0 browser.
      It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the
      Ecma General Assembly of June 1997.</p>

    <p>That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep
      it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.</p>

    <p>The third edition of the Standard introduced powerful regular expressions, better string handling, new control statements, try/catch exception handling, tighter definition of errors, formatting for numeric output and minor changes in anticipation
      of forthcoming internationalisation facilities and future language growth. The third edition of the ECMAScript standard was adopted by the Ecma General Assembly of December 1999 and published as ISO/IEC 16262:2002 in June 2002.</p>

  </div>


  <div id="mouse-wrap">Mouse coordinates: <span id="mouse">...</span></div>
  <div id="info"></div>


  <script>
    let props = {
      geometry: ['clientLeft', 'clientTop', 'clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight', 'scrollWidth', 'scrollHeight'],
      scroll: ['scrollLeft', 'scrollTop'],
      offsetParent: ['offsetParent', 'offsetLeft', 'offsetTop']
    };

    info.innerHTML = '<h3>Click to see the value:</h3>';
    for (let k in props) {
      info.innerHTML += `<h4>${k}</h4>`;
      let prop = props[k];
      for (let i = 0; i < prop.length; i++) {
        info.innerHTML += "<span class='key'>" + prop[i] + '</span>: <span id="' + prop[i] + '">&nbsp;</span>' + " "
        i++;
        if (i < prop.length) {
          info.innerHTML += "<span class='key'>" + prop[i] + '</span>: <span id="' + prop[i] + '">&nbsp;</span>';
        }
        info.innerHTML += "<br/>";

      }
    }

    document.onclick = function(event) {
      let target = event.target;
      if (!target.classList.contains('key')) return;

      let prop = target.innerHTML;
      let value = example[prop];
      value = value.tagName || value;
      document.getElementById(prop).innerHTML = value;
    };


    document.onmousemove = function(e) {
      document.getElementById('mouse').innerHTML = Math.round(e.clientX) + ':' + Math.round(e.clientY);
    };
  </script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

注意滚动条

上图演示了元素具有滚动条这种最复杂的情况。一些浏览器(并非全部)通过从内容(上面标记为 "content width")中获取空间来为滚动条保留空间。

因此,如果没有滚动条,内容宽度将是 300 px,但是如果滚动条宽度是 16px(不同的设备和浏览器,滚动条的宽度可能有所不同),那么还剩下 300 - 16 = 284px,我们应该考虑到这一点。这就是为什么本章的例子总是假设有滚动条。如果没有滚动条,一些计算会更简单。

文本可能会溢出到 padding-bottom

在我们的插图中的 padding 中通常显示为空,但是如果元素中有很多文本,并且溢出了,那么浏览器会在 padding-bottom 处显示“溢出”文本,这是正常现象。

几何

这是带有几何属性的整体图片:

这些属性的值在技术上讲是数字,但这些数字其实是“像素(pixel)”,因此它们是像素测量值。

让我们从元素外部开始探索属性。

offsetParent,offsetLeft/Top

这些属性很少使用,但它们仍然是“最外面”的几何属性,所以我们将从它们开始。

offsetParent 是最接近的祖先(ancestor),在浏览器渲染期间,它被用于计算坐标。

最近的祖先为下列之一:

  1. CSS 定位的(positionabsoluterelativefixed),
  2. <td><th><table>
  3. <body>

属性 offsetLeft/offsetTop 提供相对于 offsetParent 左上角的 x/y 坐标。

在下面这个例子中,内部的 <div><main> 作为 offsetParent,并且 offsetLeft/offsetTop 让它从左上角位移(180):

<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(注意:这是一个数字,不是字符串 "180px")
  alert(example.offsetTop); // 180
</script>
1
2
3
4
5
6
7
8
9
10

有以下几种情况下,offsetParent 的值为 null

  1. 对于未显示的元素(display:none 或者不在文档中)。
  2. 对于 <body><html>
  3. 对于带有 position:fixed 的元素。

offsetWidth/Height

现在,让我们继续关注元素本身。

这两个属性是最简单的。它们提供了元素的“外部” width/height。或者,换句话说,它的完整大小(包括边框)。

对于我们的示例元素:

  • offsetWidth = 390 — 外部宽度(width),可以计算为内部 CSS-width(300px)加上 padding(2 * 20px)和 border(2 * 25px)。
  • offsetHeight = 290 — 外部高度(height)。

对于未显示的元素,几何属性为 0/null

仅针对显示的元素计算几何属性。

如果一个元素(或其任何祖先)具有 display:none 或不在文档中,则所有几何属性均为零(或 offsetParentnull)。

例如,当我们创建了一个元素,但尚未将其插入文档中,或者它(或它的祖先)具有 display:none 时,offsetParentnull,并且 offsetWidthoffsetHeight0

我们可以用它来检查一个元素是否被隐藏,像这样:

function isHidden(elem) {
  return !elem.offsetWidth && !elem.offsetHeight;
}
1
2
3

请注意,对于会展示在屏幕上,但大小为零的元素,它们的 isHidden 返回 true

clientTop/Left

在元素内部,我们有边框(border)。

为了测量它们,可以使用 clientTopclientLeft

在我们的例子中:

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

……但准确地说 — 这些属性不是边框的 width/height,而是内侧与外侧的相对坐标。

有什么区别?

当文档从右到左显示(操作系统为阿拉伯语或希伯来语)时,影响就显现出来了。此时滚动条不在右边,而是在左边,此时 clientLeft 则包含了滚动条的宽度。

在这种情况下,clientLeft 的值将不是 25,而是加上滚动条的宽度 25 + 16 = 41

这是希伯来语的例子:

clientWidth/Height

这些属性提供了元素边框内区域的大小。

它们包括了 "content width" 和 "padding",但不包括滚动条宽度(scrollbar):

在上图中,我们首先考虑 clientHeight

这里没有水平滚动条,所以它恰好是 border 内的总和:CSS-width 200px 加上顶部和底部的 padding(2 * 20px),总计 240px

现在 clientWidth — 这里的 "content width" 不是 300px,而是 284px,因为被滚动条占用了 16px。所以加起来就是 284px 加上左侧和右侧的 padding,总计 324px

如果这里没有 padding,那么 clientWidth/Height 代表的就是内容区域,即 border 和 scrollbar(如果有)内的区域。

因此,当没有 padding 时,我们可以使用 clientWidth/clientHeight 来获取内容区域的大小。

scrollWidth/Height

这些属性就像 clientWidth/clientHeight,但它们还包括滚动出(隐藏)的部分:

在上图中:

  • scrollHeight = 723 — 是内容区域的完整内部高度,包括滚动出的部分。
  • scrollWidth = 324 — 是完整的内部宽度,这里我们没有水平滚动,因此它等于 clientWidth

我们可以使用这些属性将元素展开(expand)到整个 width/height。

像这样:

// 将元素展开(expand)到完整的内容高度
element.style.height = `${element.scrollHeight}px`;
1
2

点击按钮展开元素:

text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text

scrollLeft/scrollTop

属性 scrollLeft/scrollTop 是元素的隐藏、滚动部分的 width/height。

在下图中,我们可以看到带有垂直滚动块的 scrollHeightscrollTop

换句话说,scrollTop 就是“已经滚动了多少”。

scrollLeft/scrollTop 是可修改的

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

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

Click
Me
1
2
3
4
5
6
7
8
9

scrollTop 设置为 0 或一个大的值,例如 1e9,将会使元素滚动到顶部/底部。

不要从 CSS 中获取 width/height

我们刚刚介绍了 DOM 元素的几何属性,它们可用于获得宽度、高度和计算距离。

但是,正如我们在 样式和类 一章所知道的那样,我们可以使用 getComputedStyle 来读取 CSS-width 和 height。

那为什么不像这样用 getComputedStyle 读取元素的 width 呢?

let elem = document.body;

alert( getComputedStyle(elem).width ); // 显示 elem 的 CSS width
1
2
3

为什么我们应该使用几何属性呢?这里有两个原因:

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

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

    <span id="elem">Hello!</span>
    
    <script>
    *!*
      alert( getComputedStyle(elem).width ); // auto
    */!*
    </script>
    
    1
    2
    3
    4
    5
    6
    7

    从 CSS 的观点来看,width:auto 是完全正常的,但在 JavaScript 中,我们需要一个确切的 px 大小,以便我们在计算中使用它。因此,这里的 CSS 宽度没什么用。

还有另一个原因:滚动条。有时,在没有滚动条的情况下代码工作正常,当出现滚动条时,代码就出现了 bug,因为在某些浏览器中,滚动条会占用内容的空间。因此,可用于内容的实际宽度小于 CSS 宽度。而 clientWidth/clientHeight 则会考虑到这一点。

……但是,使用 getComputedStyle(elem).width 时,情况就不同了。某些浏览器(例如 Chrome)返回的是实际内部宽度减去滚动条宽度,而某些浏览器(例如 Firefox)返回的是 CSS 宽度(忽略了滚动条)。这种跨浏览器的差异是不使用 getComputedStyle 而依靠几何属性的原因。

如果你的浏览器保留了滚动条的空间(大多数 Windows 中的浏览器),那么你可以在下面测试它。

[iframe src="cssWidthScroll" link border=1]

带有文本的元素具有 width:300px

在桌面 Windows 操作系统上,Firefox、Chrome、Edge 都为滚动条保留了空间。但 Firefox 显示的是 300px,而 Chrome 和 Edge 显示较少。这是因为 Firefox 返回 CSS 宽度,其他浏览器返回“真实”宽度。

请注意,所描述的差异只是关于从 JavaScript 读取的 getComputedStyle(...).width,而视觉上看,一切都是正确的。

总结

元素具有以下几何属性:

  • offsetParent — 是最接近的 CSS 定位的祖先,或者是 tdthtablebody
  • offsetLeft/offsetTop — 是相对于 offsetParent 的左上角边缘的坐标。
  • offsetWidth/offsetHeight — 元素的“外部” width/height,边框(border)尺寸计算在内。
  • clientLeft/clientTop — 从元素左上角外角到左上角内角的距离。对于从左到右显示内容的操作系统来说,它们始终是左侧/顶部 border 的宽度。而对于从右到左显示内容的操作系统来说,垂直滚动条在左边,所以 clientLeft 也包括滚动条的宽度。
  • clientWidth/clientHeight — 内容的 width/height,包括 padding,但不包括滚动条(scrollbar)。
  • scrollWidth/scrollHeight — 内容的 width/height,就像 clientWidth/clientHeight 一样,但还包括元素的滚动出的不可见的部分。
  • scrollLeft/scrollTop — 从元素的左上角开始,滚动出元素的上半部分的 width/height。

除了 scrollLeft/scrollTop 外,所有属性都是只读的。如果我们修改 scrollLeft/scrollTop,浏览器会滚动对应的元素。

元素大小和滚动 has loaded