Javascript 元素大小和滚动

2023-02-17 10:54 更新

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

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

示例元素

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

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

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

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


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

注意滚动条

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

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

文本可能会溢出到 ​padding-bottom​ 中

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

几何

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


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

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

offsetParent,offsetLeft/Top

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

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

最近的祖先为下列之一:

  1. CSS 定位的(​position​ 为 ​absolute​、​relative​、fixed 或 ​sticky​),
  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>


有以下几种情况下,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 或不在文档中,则所有几何属性均为零(或 offsetParent 为 null)。

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

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

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

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

clientTop/Left

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

为了测量它们,可以使用 clientTop 和 clientLeft

在我们的例子中:

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


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

有什么区别?

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

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

这是希伯来语的例子:


clientWidth/Height

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

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


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

这里没有水平滚动条,所以它恰好是 border 内的总和:CSS-height 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`;


scrollLeft/scrollTop

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

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


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

scrollLeft/scrollTop​ 是可修改的

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

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


将 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. 首先,CSS ​width/height​ 取决于另一个属性:​box-sizing​,它定义了“什么是” CSS 宽度和高度。出于 CSS 的目的而对 ​box-sizing​ 进行的更改可能会破坏此类 JavaScript 操作。
  2. 其次,CSS 的 ​width/height​ 可能是 ​auto​,例如内联(inline)元素:
  3. <span id="elem">Hello!</span>
    
    <script>
      alert( getComputedStyle(elem).width ); // auto
    </script>

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

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

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

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

https://zh.js.cx/article/size-and-scroll/cssWidthScroll/

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

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

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

总结

元素具有以下几何属性:

  • offsetParent​ —— 是最接近的 CSS 定位的祖先,或者是 ​td​,​th​,​table​,​body​。
  • 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,浏览器会滚动对应的元素。

任务


相对于底部滚动了多少?

重要程度: 5

elem.scrollTop 属性是从顶部滚动出来的部分的大小。如何获得底部滚动的大小(我们称其为 scrollBottom)?

编写适用于任意 elem 的代码。

P.S. 请检查你的代码:如果没有滚动,或元素底部已经完全滚动完成,那么它应该返回 0


解决方案

解决方案:

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

换句话说:(完全高度)减去(已滚出顶部的高度)减去(可见部分的高度)—— 得到的结果就是滚动出来的底部的部分。


滚动条的宽度是多少?

重要程度: 3

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

对于 Windows,它通常在 12px 和 20px 之间变化。如果浏览器没有为其保留任何空间(滚动条以半透明的形式处于文本上面,也是可能发生的),那么它可能是 0px

P.S. 该代码应适用于任何 HTML 文档,而不依赖于其内容。


解决方案

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

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

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

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

// 必须将其放入文档(document)中,否则其大小将为 0
document.body.append(div);
let scrollWidth = div.offsetWidth - div.clientWidth;

div.remove();

alert(scrollWidth);

将小球置于区域(field)中心

重要程度: 5

源文件的效果如下:


区域(field)的中心坐标是多少?

计算它们,并将小球置于绿色的区域(field)中心:


  • 该元素应该通过 JavaScript 移动,而不是 CSS。
  • 该代码应该适用于任何大小的球(​10​、​20​、​30​ 像素)以及任意大小的区域(field),而不应该绑定到给定值。

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

打开一个任务沙箱。


解决方案

球具有 position:absolute。这意味着它的 left/top 坐标是从最近的具有定位属性的元素开始测量的,这个元素即 #field(因为它有 position:relative)。

坐标从场(field)的左上角内侧开始:


内部的场(field)的 width/height 是 clientWidth/clientHeight。所以场(field)的中心坐标为 (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';

这是它将显示出来的效果:


为了使球的中心与场(field)的中心重合,我们应该把球向左移动球宽度的一半,并向上移动球高度的一半:

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> 没有 width/height 时,代码将无法可靠地工作:

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

当浏览器不知道图片的 width/height(通过标签 attribute 或 CSS)时,它会假定它们等于 0,直到图片加载完成。

因此,在图片加载完成之前,ball.offsetWidth 的值为 0。这会导致上面的代码中会有错误的坐标。

在第一次加载完成后,浏览器通常会缓存该图片,并在下一次加载时,浏览器会立即拥有该图片的大小。但是在第一次加载时,ball.offsetWidth 的值为 0

我们应该通过在 <img> 中添加 width/height 来解决这个问题:

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

……或者在 CSS 中提供大小:

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

使用沙箱打开解决方案。


CSS width 与 clientWidth 的不同点

重要程度: 5

getComputedStyle(elem).width 与 elem.clientWidth 之间有什么不同点?

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


解决方案

不同点:

  1. clientWidth​ 值是数值,而 ​getComputedStyle(elem).width​ 返回一个以 ​px​ 作为后缀的字符串。
  2. getComputedStyle​ 可能会返回非数值的 width,例如内联(inline)元素的 ​"auto"​。
  3. clientWidth​ 是元素的内部内容区域加上 padding,而 CSS width(具有标准的 ​box-sizing​)是内部内容区域,不包括 padding
  4. 如果有滚动条,并且浏览器为其保留了空间,那么某些浏览器会从 CSS width 中减去该空间(因为它不再可用于内容),而有些则不会这样做。​clientWidth​ 属性总是相同的:如果为滚动条保留了空间,那么将减去滚动条的大小。


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号