你必须知道的 clientWidth, offsetWidth, scrollWidth.

8,253 阅读9分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

前言: 在公司移动端项目中,我需要十分频繁的和 DOM 元素的各种 widthheight 打交道。但是这里有这么多关于 width 的属性,它们之间的区别到底体现在哪里?这是我刚刚接触移动端项目十分头疼的一个问题。经过几天的查阅,终于搞明白了这几个的不同之处,特来分享。

tips: 本文意指让你自己通过联想记牢这几个属性的区别,而不是单纯地表达它们概念上的不同,故篇幅会较长,还希望读者耐下心细细品读☕️。


一. 前期准备

  1. 在学习这几个概念之前,我强烈建议你用你喜欢的框架或者工具去简单写一个 <div> 元素来更深刻的体会我接下来讲解的内容。不用特别复杂,十分简单的,带有宽度高度的 div 即可。
    image.png
    在页面上的效果如下。
    image.png

  2. 接下来你需要进入到开发者页面,选中刚刚我们创造的这个 div 元素。
    image.png

  3. 然后你需要从右边的选项卡中找到 Properties(dom 属性) 这一项中筛选出带有 width 的几个属性。
    width.gif
    接下来你将会频繁的看到这三个属性的变化。

二. clientWidth

  1. 三个属性中我们先对 clientWidth 初步讲解一下。

  2. 我相信大家初次去 MDN 查阅这个 Css 属性的时候,一定看到过下面这张图。
    image.png

  3. 初次看到这张图,感觉它什么都讲了,但是又好像不知道它到底想表达什么。如果你也有这样的疑惑,本文将带你一步步去体会它到底想表达什么。

  4. 首先切忌死记硬背属性名,我们需要十分明确的知道这个属性名为什么叫 clientWidth 而不是叫 小猪佩奇Width。下面是这个单词的翻译,我认为 client 这个单词在这里语境中最佳的翻译应该为---"客户端,用户端"
    image.png

  5. ok,我知道你可能还是有疑惑,别着急,我们一步一步来。经过上面 width 属性搜索结果可知,聪明的你可能已经发现 clientWidth 的值不就是我们给这个 box 设置的 width 的属性值 100 吗?
    image.png

  6. 是的,对了,但是不完全对。这里更好的说法应该是:“按照我们现在的布局和样式,clientWidthwidth是相等的。”对,只能称作它们的是相等的,而不能说这两个属性是全等的。

  7. 那这个 clientWidth 到底该怎么去理解呢?“客户端的宽度用户端的宽度?”总感觉哪里怪怪的。🤔

  8. 在这里我先抛出一个我个人认为比较贴切的说法:“clientWidth 指的是一个 dom 元素中,当前状态下可以被用户即时看到的宽度(理解为可视区域的宽度或许也可以)。

  9. ”什么意思?假设你现在面前放着一个空的正方体的快递盒子📦。里面填充了一个大小恰好一模一样的黑色正方体铁块。那么你目前看到的这个铁块的宽度就称作快递盒子(box)的 clientWidth

  10. 现在我们把铁块取出来,得到了一个空的盒子对吧?现在我在盒子周围贴上了一圈10厘米宽的白色泡沫板。然后又找来了一个小一号的,但是又恰好可以一起塞进箱子里的铁块。体现到我们的例子上就是 box 加上了 10px 的 border 属性。
    image.png
    image.png
    然后我们再去 Properties 选项卡查看这三个属性。
    image.png

  11. 我们发现 clientWidth 的宽度减少了 20px。(我们不是只设置了 10px 吗?怎么一下子减少了 20px 呢?我希望你不要忘了,border 如果没指定具体方向的话,它会在上下左右都设定一份...就像你在快递盒子四个边都塞了一份泡沫板)

  12. 此时你能看到的铁块的宽度就是 clientWidth,我们好像可以得出一个结论,clientWidth = width- 左border - 右border。不对,不能这么早下结论,我们好像遗忘了一个一个重要属性 padding。我们目前还不知道 padding 会不会影响 clientWidht 的值。ok,那我们就验证一下,现在我们给这个盒子添加一个 10px 的 padding 看一下会不会发生什么变化。
    image.png
    可以看出,我们加的 padding 好像确实没有影响到 clientWidth 的宽度。 image.png
    我们刚刚得出的 clientWidth = width- 左border - 右border 看来还是站得住脚的。

  13. 其实在这里我更想表达的是,我们在理解 padding 这个属性的时候,我们其实可以把它理解为内容区的一部分。因为 padding 属性其实也是受到了 bg-color(background-color) 的影响,而 border 属性是没有受到 bg 影响而变为黑色的。这也是为什么 padding 没有 padding-color 这一属性的原因,因为它本身是被当成了内容区的一一部分,它的颜色是随着内容区(也就是 bg-color) 而改变的原因。

  14. 这时候我们再去看这张图,这里 padding 为什么有颜色?这其实是 MDN 为了更好的去表现出 padding 属性而“特意”添加上了不同颜色的。
    image.png
    所以, clientWidth 这一属性更像是描述用户可以直接看到的内容区域。

  15. tips: 这里需要特别注意!!我们现在的结论是站在 box-sizing 属性为 border-box 的前提下的,后面我会讲解到 box-sizingcontent-boxclientWidth 的不同。

三. offsetWidth

  1. 关于 offsetWidth,其实 MDN 有这样一句话能够很完美的表现出它想表达的意思。
    image.png

  2. 它的概念其实非常非常简单,就是在 box-sizing:border 的时候 offsetWidth 其实就等于 dom 元素的 width。不知道你是否遗忘了 box-sizing:content 这个标准盒子模型的概念。让我们切换一下 box-sizing 的属性,变为 content 来看看这个属性值有什么变化。
    image.png
    image.png
    可以很清楚的看到,在 box-sizing:conetent 的时候 offsetWidth= width + 左border + 右border + 左padding + 右padding

这是因为在 content-box 的情况下,我们设置的 dom 元素的宽度其实仅仅只是内容区的宽度。我们设置的 border 和 padding 都是由内容区向外扩张。

border-box 下设置的 borderpadding 都是由盒子向内部收缩来给 border 或者 padding “腾地方”。

  1. offsetWidth 这个属性是为了表达了盒子的真实物理宽度。所以它的计算方式会根据 box-sizing 的不同而不同。

  2. 在这里你可能会好奇,为什么 clientWidth 也跟着变了?其实根据我们上面讲的:“padding 其实是算作特殊的内容区”来分析,很容易就可以想到 clientWidth= 100px+ 10px + 10px, 所以就等于了 120px。

四. scrollWidth

  1. 为什么在上面我压根没提这个属性呢?因为这个属性的触发条件很特殊,它只有在特殊的场景下才能体现出和 clientWidth 的区别。(tips:它确实是需要对标 clientWidth 的,先不要疑惑,接着往下看你就明白为什么对标的是 clientWidth 而不是 offsetwidht 了。)

  2. 那这个“特殊场景”指的是什么呢?其实你应该非常熟悉----overflow

  3. ok,那我们怎么创建这个特殊场景呢?其实非常非常简单,你在这个 100px 宽度的盒子里多打几个字就可以。 我们简单设置一下字体的颜色和大小。注意:这里我们已经把 box-sizing 切换成了 border-box,这里为了表现溢出,我们需要设置一个特殊的属性 white-space:nowrap

<template>
  <div class="box">希望大家可以从我博客中学到一些新的知识</div>
</template>

<style scoped>
.box {
  width: 100px;
  height: 100px;
  font-size: 20px;
  white-space: nowrap;
  color: red;
  background: black;

  border: 10px solid blue; /* 10cm 的泡沫板 */
  padding: 10px;
}
</style>
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e4ae5bf5960248fba5bae33d8c9bd11e~tplv-k3u1fbpfcp-watermark.image?)
  1. 很清楚的看到,由于我们的字体宽度过大,导致盒子塞不下,但是我们又没有设置溢出场景的处理方法,就导致了现在的样子。
    image.png

  2. 我想你已经迫不及待的去 Properties 去查看这几个属性了,但是你有可能一脸疑惑的回来。
    image.png
    什么情况,这 scrollWidth 也没变啊!我怀疑你在骗我。😠

  3. 其实从 scroll 这个词我们猜或许也能猜到什么。
    image.png
    既然和“滚动”有关,那么现在页面发生滚动了吗?没有,怎么才可以滚动呢?没错 overflow-auto(其它值也一样,只不过 auto 方便一点)。

  4. 设置了 overflow 的处理后,我们现在页面应该是下面这个样子。
    image.png
    我们再去查看这三个属性,你会发现 scrollWidth 已经变了。
    image.png

  5. 那 390 这个数值是怎么来的呢?这个其实数值要根据你实际项目造成溢出的原因来分析的。但是 scrollWidth 这个属性实际上代表的是“dom元素内容区的真实宽度”。什么意思呢?让我们先把 overflow-auto 属性删除,恢复成它原来溢出的样子。
    image.png

  6. 然后我们需要通过一些“特殊的手法”去测量一下这个值是体现在哪里。
    overflow.gif
    390 实际上就是文字区域+ padding 区域的宽度。说的直白一点,其实就是内容区(content)。(我再强调一下,你可以把 padding 理解为特殊的内容区

  7. 没错,到这里我想你大概已经明白了,实际上 scrollWidth 代表的就是内容区的真实宽度。在这里我们就需要把 clientWidth 拎出来讲一下了。同样是表达内容区宽度的。

1.在内容区没有发生溢出的情况下,scrollWidth = clientWidth 因为它们都是代表内容区的宽度。
2.在内容区发生了溢出,并且设置了 overflow-scroll 之类的属性的情况下,clientWidth 代表dom 当前状态下,实际上展示在可视区域的 内容区(content) 的宽度,而 scrollWidth 则代表了真实的内容区的宽度,包括了那些没有展现在用户面前的,需要滚动才可以看到的内容的宽度。这时候 scrollWidth= clientWidth + 溢出的内容区的宽度

  1. 所以 clientWidth 更确切的中文翻译应该是---客户端可视区域内,内容区的宽度。

结语

这三个属性所对应的 offsetHeightclientHeighscrollHeight 都是同样的道理,在这里就不重复赘述了,希望读者可以自己举一反三自行推导这三个属性代表什么意思。

从学习这几个属性的过程中才真正体会到:“看一千遍文档,不如自己动手实践一遍”的道理。还希望大家也可以跟着自己敲一敲,真的不难。与君共勉才是我写这篇文章的初衷~