元素的尺寸与滚动

JavaScript 中存在许多属性让我们能够读取元素的宽度、高度或其他具有几何特征的信息。

在移动或定位 JavaScript 元素时,我们经常需要它们来计算元素的坐标。

示例元素

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

<div id="example">
  ...Text...
</div>
<style>
  #example {
    width: 300px;
    height: 200px;
    border: 25px solid #E8C48F;
    padding: 20px;
    overflow: auto;
  }
</style>

它有边框、内填充和滚动条等全部特征属性,但是这不包括外边距,因为它不是元素本身的一部分,并且它们没什么特殊的属性。

元素就像这样:

你可以 在sandbox中打开文档.

注意滚动条

上例图片演示了当元素有滚动条时最复杂的情况,一些浏览器(不是全部)通过牺牲内容空间来保留滚动条。

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

文本可能会溢出到 padding-bottom

内填充通常在插图中显示的是空的,但是如果元素中有很多文本,并且溢出,那么浏览器在 padding-bottom 中显示“溢出”文本,这可以在示例中看到。但是填充物仍然存在,除非另有说明。

几何学

提供宽度、高度和其他几何形状的元素属性通过数值来计算。它们被假定为像素。

以下是显示总体情况的图片:

它们有很多属性,很难将它们全部放在单个图片中,但是它们的值很简单,容易理解。

让我们从元素的外部开始探索它们。

offsetParent, offsetLeft/Top

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

offsetParent 是最近的祖先元素:

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

在大多数实际情况下,我们可以使用 offsetParent 来获得最近的 CSS 定位祖先。offsetLeft/offsetTop 提供相对于元素左上角的 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 (note: a number, not a string "180px")
  alert(example.offsetTop); // 180
</script>

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

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

offsetWidth/Height

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

这两个属性是最简单的。它们提供元素的“外部”宽度/高度。换句话说,它的完整大小包括边框。

在我们的示例元素中:

  • offsetWidth = 390 — 外部宽度,计算方法是内部 css 宽度(300px)加上内填充(2 * 20px)和边框宽度(2 * 25px)。
  • offsetHeight = 290 — 外部高度。
未显示的几何元素的属性值为 0/null

几何属性仅为显示出来的元素计算。

如果元素(或其任何祖先)在文档中显示为 display:none 或本身不在文档中,则所有几何属性都是 0 或者值为 null,这取决于它是什么。

例如,offsetParentnull,并且 offsetWidthoffsetHeight0

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

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

请注意,isHidden 返回 true 表示元素不在页面中显示,但不代表尺寸为 0(like an empty <div>)。

clientTop/Left

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

要测量它们,可使用 clientTopclientLeft

在例子中:

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

…但确切地说,它们不是边框,而是内侧与外侧的相对坐标。

有什么区别?

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

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

clientWidth/Height

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

他们包含内容宽度和内填充宽度,但不包括滚动条宽度:

在上面的图片中,我们首先考虑 clientHeight:这很容易计算。没有水平滚动条,所以它正好是边界内的总和:CSS 高度 200px 加上顶部和底部的内填充宽度(2 * 20px),总计 240px

这种情况下的 clientWidth — 这里的内容宽度不是 300px,而是284px,因为 16px 是滚动条的宽度。所以加起来就是 284px 加上左右内间距,总和 324px

如果没有内间距,那么 clientWidth/Height 就是代表内容的宽度, 这里的内容指的是内间距和滚动条以内 (如果还有其他的)。

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

scrollWidth/Height

  • 属性 clientWidth/clientHeight 只考虑元素的可见部分。
  • 属性 scrollWidth/scrollHeight 还包括滚动(隐藏)部分:

上图:

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

我们可以使用这些属性将元素扩展到其完全宽度/高度。

就像这样:

// 扩展元素高度到完全高度
element.style.height = `${element.scrollHeight}px`;

单击按钮展开元素:

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 是元素隐藏、滚动部分的宽度/高度。

下面的图片我们可以看到 scrollHeightscrollTop 是一个垂直滚动块的属性。

换种说法,scrollTop 就是 “滚动了多少” 的意思。

scrollLeft/scrollTop 可修改

大多数几何属性是只读的,但是 scrollLeft/scrollTop 可以改变,浏览器将会直接滚动元素。

如果单击下面的元素,代码 elem.scrollTop += 10 将会执行,这使得元素向下滚动 10px

Click
Me
1
2
3
4
5
6
7
8
9

scrollTop 设置为 0Infinity 将使元素分别滚动到顶部/底部。

不要从 CSS 中获取宽高

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

但是,正如我们从《信息:样式和类》一章所知道的,我们可以使用 getComputedStyle 来读取CSS的高度和宽度。

那么为什么不像这样读取一个元素的高度呢?

let elem = document.body;

alert( getComputedStyle(elem).width ); // show CSS width for elem

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

  1. 首先,CSS 宽度/高度取决于另一个属性:box-sizing,它定义了 “什么是” CSS 宽度和高度。用作 CSS 样式的 box-sizing 的更改可能会破坏这样的 JavaScript 定义。

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

    <span id="elem">Hello!</span>
    
    <script>
      alert( getComputedStyle(elem).width ); // auto
    </script>

    从 CSS 的观点来看,width:auto 是完全正常的,但是在 JavaScript 中,我们需要一个精确的 px 大小,以便我们在计算过程中使用它,所以 CSS 宽度根本没用。

还有一个原因:滚动条。有时,没有滚动条的代码工作得很好,因为它在一些浏览器中占据了内容的一部分空间。因此,内容的实际宽度比 CSS 宽度要小。而 clientWidth/clientHeight 考虑到这一点。

但是,getComputedStyle(elem).width 的情况是不同的。一些浏览器(例如 Chrome)返回真正的内部宽度,这种情况不考虑滚动条,以及其中一些(例如Firefox)— CSS 宽度(忽略滚动条)。这样的跨浏览器差异是不使用 getComputedStyle 样式的原因,而是依赖于几何属性。

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

包含文本的元素有这样的样式 width:300px

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

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

总结

元素具有以下几何属性:

  • offsetParent — 是最近的有定位属性的祖先元素,或者是 tdthtablebody
  • offsetLeft/offsetTop — 是相对于 offsetParent 的左上角边缘坐标。
  • offsetWidth/offsetHeight — 元素的“外部”宽/高 ,边框尺寸计算在内。
  • clientLeft/clientTop — 从元素左上角外部到内部的距离,对于从左到右渲染元素的操作系统,它始终是左/顶部边界的宽度,而对于从右到左的操作系统,垂直滚动条在左边,所以 clientLeft 也包括滚动条的宽度。
  • clientWidth/clientHeight — 内容的宽度/高度,包括内间距,但没有滚动条。
  • scrollWidth/scrollHeight — 内容的宽度/高度,包括可滚动的可视区域外的尺寸,也包括内间距,但不包括滚动条。
  • scrollLeft/scrollTop — 从左上角开始的元素的滚动部分的宽度/高度。

除了 scrollLeft/scrollTop 之外,所有属性都是只读的。如果更改,浏览器会使元素滚动。

任务

重要程度: 5

elem.scrollTop 属性是从顶部滚动出来的部分的大小,如何获得 scrollBottom —— 从底部滚动的尺寸?

编写适用于任意元素的代码。

P.S. 请检查您的代码:如果没有滚动条或元素完全向下滚动,那么它应该返回 “0”。

解决方式:

let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;

换句话说:(完全高度)减去(已滚动的高度)减去(可见部分的高度)— 得到的结果就是下方隐藏部分的高度。

重要程度: 3

编写返回标准滚动条宽度的代码。

对于Windows,它通常在 12px20px 之间变化。如果浏览器不为它保留任何空间,那么它可能是 0px

P.S. 代码应该适用于任何HTML文档,且不依赖于它的内容元素。

为了获得滚动条宽度,我们可以创建一个带有滚动条的元素,但是没有边框和内间距。

然后,它的完全宽度 offsetWidth 和内容宽度 clientWidth 之间的差就是滚动条的宽度:

// 创建一个包含滚动条的块
let div = document.createElement('div');

div.style.overflowY = 'scroll';
div.style.width = '50px';
div.style.height = '50px';

// 必须添加到文档中,否则尺寸为 0
document.body.append(div);
let scrollWidth = div.offsetWidth - div.clientWidth;

div.remove();

alert(scrollWidth);
重要程度: 5

这是源文件:

有效范围的中心坐标是多少?

计算并将小球置于有效范围中心:

  • 元素应该通过 JavaScript 移除,而不是 CSS。
  • 代码应该在不限制小球尺寸(102030 像素)或任何有效范围内的尺寸的情况下正常运行,而不是直接为元素绑定尺寸。

P.S. 当然了,置中的操作可以通过 CSS 完成,但是这里我们需要通过 JavaScript 完成。此外,当必须使用 JavaScript 时,我们可能会遇到其他话题或更加复杂的情况,这里我们只是做一个 “热身”。

打开一个任务沙箱。

为小球设置 position:absolute。这意味着它的 left/top 坐标根据相对它最近的并且设置了定位的元素来测量,这个元素的有效范围就是 #field(因为它有 position:relative)。

坐标从设置了相对定位的最近的元素的左上角开始:

该元素内容区域的宽/高是 clientWidth/clientHeight,所以该元素有效范围的中心的坐标为 (clientWidth/2, clientHeight/2)

…不过如果我们将 ball.style.left/top 设置为上面计算出的值,那么最终的效果与有效范围的中心重合的不是小球的中心,而是小球左上角的坐标:

ball.style.left = Math.round(field.clientWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2) + 'px';

这是它将显示出的效果:

为了把球中心和有效范围中心对准,我们应该把球移到左边宽度的一半,把 top 设置为有效范围高度的一半:

ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';

注意:陷阱!

如果 <img> 没有宽/高,代码将无法正常工作:

<img src="ball.png" id="ball">

当浏览器还不知道图片的宽/高(图片的尺寸可能来自标签属性或 CSS)的时候它会假设它们的尺寸为 0直到图片加载完成。

实际使用过程中,浏览器会在图片第一次加载完成后缓存该图片,方便下次再次访问时立即显示图片。

但是在第一次加载时 ball.offsetWidth 的值为 0,这会导致错误的坐标出现。

此时我们应该为 <img> 添加 width/height 属性:

<img src="ball.png" width="40" height="40" id="ball">

…或者在 CSS 中提供尺寸:

#ball {
  width: 40px;
  height: 40px;
}

使用沙箱打开解决方案。

重要程度: 5

getComputedStyle(elem).widthelem.clientWidth 之间的差别在哪?

指出至少三种不同点,越多越好。

不同点:

  1. clientWidth 值是数值,然而 getComputedStyle(elem).width 返回一个包含 px 的字符串。
  2. getComputedStyle 可能返回非数值的结果,例如内联元素的 "auto"
  3. clientWidth 是元素的内部内容区域加上内间距,而 CSS 宽度(具有标准的 box-sizing)是内部不包括内间距的空间区域。
  4. 如果有一个滚动条,一般浏览器保留它的空间,有的浏览器从 CSS 宽度中减去这个空间(因为它不再用于内容),而有些则不这样做。clientWidth 属性总是相同的:如果保留了滚动条,那么它的宽度将被删去。
教程路线图

评论

在评论之前先阅读本内容…
  • 如果你发现教程有错误,或者有其他需要修改和提升的地方 — 请 提交一个 GitHub issue 或 pull request,而不是在这评论。
  • 如果你对教程的内容有不理解的地方 — 请详细说明。
  • 使用 <code> 标签插入只有几个词的代码,插入多行代码可以使用 <pre> 标签,对于超过 10 行的代码,建议你使用沙箱(plnkrJSBincodepen…)