2022 年 6 月 26 日

元素大小和滚动

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

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

示例元素

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

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

它有边框、填充和滚动。完整的特性集。没有边距,因为它们不是元素本身的一部分,也没有针对它们的特殊属性。

元素看起来像这样

您可以在沙盒中打开文档

注意滚动条

当元素有滚动条时,上图展示了最复杂的情况。一些浏览器(并非全部)通过从内容中获取空间(上面标记为“内容宽度”)来为其保留空间。

因此,如果没有滚动条,内容宽度将为 300px,但如果滚动条宽 16px(宽度可能因设备和浏览器而异),则只有 300 - 16 = 284px,我们应该考虑到这一点。这就是本章中的示例假设存在滚动条的原因。没有它,一些计算会更简单。

padding-bottom 区域可以用文本填充

通常,填充在我们的插图中显示为空,但如果元素中有大量文本并且溢出,则浏览器会在 padding-bottom 中显示“溢出”文本,这是正常的。

几何

以下是具有几何属性的整体图片

从技术上讲,这些属性的值是数字,但这些数字是“像素”,因此这些是像素测量值。

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

offsetParent、offsetLeft/Top

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

offsetParent 是浏览器在渲染期间用于计算坐标的最近祖先。

这是以下之一的最近祖先

  1. CSS 定位(positionabsoluterelativefixedsticky),或
  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 (note: a number, not a string "180px")
  alert(example.offsetTop); // 180
</script>

有几种情况 offsetParentnull

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

offsetWidth/Height

现在让我们继续讨论元素本身。

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

对于我们的示例元素

  • offsetWidth = 390 - 外部宽度,可以计算为内部 CSS 宽度(300px)加上填充(2 * 20px)和边框(2 * 25px)。
  • offsetHeight = 290 - 外部高度。
对于未显示的元素,几何属性为零/空

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

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

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

我们可以使用它来检查元素是否隐藏,如下所示

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

请注意,对于屏幕上显示但大小为零的元素,此类 isHidden 会返回 true

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,但它们还包括滚动出去(隐藏)的部分

在上图中

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

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

像这样

// expand the element to the full content height
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 text text text text text text

scrollLeft/scrollTop

属性 scrollLeft/scrollTop 是元素隐藏的、滚动出去的部分的宽度/高度。

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

换句话说,scrollTop 是“向上滚动多少”。

可以修改 scrollLeft/scrollTop

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

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

单击

1
2
3
4
5
6
7
8
9

scrollTop 设置为 0 或大值(如 1e9)将分别使元素滚动到最顶部/底部。

不要从 CSS 中获取宽度/高度

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

但正如我们在章节 样式和类 中所知,我们可以使用 getComputedStyle 读取 CSS 高度和宽度。

那么为什么不使用 getComputedStyle 来读取元素的宽度,如下所示呢?

let elem = document.body;

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

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

  1. 首先,CSS width/height 取决于另一个属性: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 浏览器都会这样做),那么你可以在下面进行测试。

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

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

请注意,所描述的差异仅与从 JavaScript 中读取 getComputedStyle(...).width 有关,在视觉上,一切都正确。

总结

元素具有以下几何属性

  • offsetParent – 是最近的定位祖先或 tdthtablebody
  • offsetLeft/offsetTop – 相对于 offsetParent 的左上角的坐标。
  • offsetWidth/offsetHeight – 元素的“外部”宽度/高度,包括边框。
  • clientLeft/clientTop – 从左上角外角到左上角内角(内容 + 内边距)的距离。对于从左到右的操作系统,它们始终是左右边框的宽度。对于从右到左的操作系统,垂直滚动条位于左侧,因此 clientLeft 也包括其宽度。
  • clientWidth/clientHeight – 内容的宽度/高度,包括填充,但不包括滚动条。
  • scrollWidth/scrollHeight – 内容的宽度/高度,就像 clientWidth/clientHeight,但还包括元素中已滚动出去不可见的部分。
  • scrollLeft/scrollTop – 元素中已滚动出去的上半部分的宽度/高度,从其左上角开始。

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

任务

重要性:5

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

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

P.S. 请检查你的代码:如果没有滚动或元素已完全向下滚动,则应返回 0

解决方案是

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

换句话说:(总高度)减去(已滚出的顶部部分)减去(可见部分)——这正是已滚出的底部部分。

重要性:3

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

对于 Windows,它通常在 12px20px 之间变化。如果浏览器没有为其保留任何空间(滚动条半透明地覆盖在文本上,也会发生这种情况),则它可能是 0px

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

要获取滚动条宽度,我们可以创建一个带有滚动条但没有边框和填充的元素。

然后其总宽度 offsetWidth 和内部内容区域宽度 clientWidth 之间的差值将恰好是滚动条

// create a div with the scroll
let div = document.createElement('div');

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

// must put it in the document, otherwise sizes will be 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';

以下是如何显示

要将球的中心与该区域的中心对齐,我们应将球向左移动其宽度的一半,并向上移动其高度的一半

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。这会导致上述代码中的坐标错误。

在第一次加载后,浏览器通常会缓存该图像,并且在重新加载时,它将立即具有该大小。但在第一次加载时,ball.offsetWidth 的值为 0

我们应通过将 width/height 添加到 <img> 来修复此问题

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

…或在 CSS 中提供该大小

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

在沙盒中打开解决方案。

重要性:5

getComputedStyle(elem).widthelem.clientWidth 之间有什么区别?

给出至少 3 个差异。越多越好。

差异

  1. clientWidth 是数字,而 getComputedStyle(elem).width 返回一个字符串,末尾带有 px
  2. 对于内联元素,getComputedStyle 可能返回非数字宽度,例如 "auto"
  3. clientWidth 是元素的内部内容区域加上填充,而 CSS 宽度(具有标准 box-sizing)是内部内容区域不包括填充
  4. 如果存在滚动条并且浏览器为其保留了空间,则某些浏览器会从 CSS 宽度中减去该空间(因为它不再可用于内容),而某些浏览器则不会。clientWidth 属性始终相同:如果保留了滚动条大小,则会减去该大小。
教程地图

评论

在评论之前请阅读此内容...
  • 如果您有改进建议 - 请 提交 GitHub 问题 或提交拉取请求,而不是发表评论。
  • 如果您无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标签,对于多行 - 将其包装在 <pre> 标签中,对于 10 行以上 - 使用沙盒 (plnkrjsbincodepen...)