正确获取DOM元素的大小

作为一个前端开发者,需要经常与DOM元素打交道;也经常需要根据DOM元素的大小做一些事情。但是如何正确的获取DOM元素的大小呢?在之前的工作中,我经常也只是在需要的时候查查资料,虽然当时解决了问题,但是也没有深入系统的把这些知识好好的梳理一下,形成自己的知识体系。
我写这篇文章的目的有两个:一个是怕自己以后还会忘记,如果忘记了就来看看自己写的博客;二来也是想和大家一起来探讨一下,加深我们的记忆。好了别的废话就不多说,我们来进入正文。

说明:本篇文章的代码都是在Mac操作系统的Chrome浏览器下进行的,因为部分代码的结果会因为操作系统和浏览器的不同而有所变化。

我们获取一个元素的大小,基本上是通过它的clientXoffsetXscrollX属性来获取的(其中X表示widthheightlefttop)。具体属性可以看下表:

clientX offsetX scrollX
clientWidth offsetWidth scrollWidth
clientHeight offsetHeight scrollHeight
clientLeft offsetLeft scrollLeft
clientTop offsetTop scrollTop

我们首先来说一下clientX相关的属性,clientLeft表示的是一个元素的左边框的宽度;需要注意的是,如果元素里面的内容产生了垂直的滚动条,并且里面文字的书写方向是从右向左的话,那么clientLeft包括左边的滚动条的宽度(Windows系统下的Chrome,Firefox和Opera浏览器)。我们来实践一下:
HTML部分如下:

<div class="clientLeft">这是clientLeft</div>

CSS部分如下:

.clientLeft {
    width: 100px;
    height: 100px;
    border-left: 6px solid black;
    padding: 15px;
    background-color: #999;
}

JavaScript部分如下:

// 辅助函数
const $ele = function (ele) {
    return document.querySelector(ele);
};
const $log = function () {
    console.log.apply(this, arguments);
};

// 测试clientX 属性
let clientLeft = $ele('.clientLeft');
$log('clientLeft的值是:' + clientLeft.clientLeft);

我们会看到控制台的输出如下:

clientLeft的值是:6

然后我们修改一下元素clientLeft的CSS,让它的内容是从右向左排列的,然后让内容超出;我们来试验一下:

.clientLeft {
    /* 省略上面已经写过的内容 */
    direction: rtl;
    /* 你需要将clientLeft的内容再增加一下,让里面的内容可以产生滑动 */
    overflow: scroll;
}

然后我们再看一下控制台的输出,这时候如果是Windows操作系统下的Chrome的话,就不会是6这个值了。因为笔者的电脑是Mac操作系统,所以控制台的打印还是6。
下面是我在windows系统里面的chrome浏览器的测试结果:


控制台的输出如下:

clientLeft的值是:23

接下来我们来讨论一下clientTop属性,clientTop指的是元素上边框的宽度,我们来测试一下这个属性。
HTML部分如下:

<div class="clientTop">这是clientTop,永远都不要放弃你的梦想</div>

CSS部分如下:

.clientTop {
    width: 100px;
    height: 100px;
    margin-top: 10px;
    border-top: 6px solid black;
    padding: 15px;
    background-color: #999;
}

JavaScript部分如下:

// 测试clientTop 属性
let clientTop = $ele('.clientTop');
$log('clientTop的值是:' + clientTop.clientTop);

我们可以看到,控制台的输出如下:

clientTop的值是:6

接下来我们来讨论clientWidthclientHeight属性的使用;这两个属性,一个是计算DOM元素的宽度,一个是计算高度的。不过这两个属性都只包含元素本身的宽高,以及元素的padding值,但是不包含元素的bordermargin的值,我们来测试一下。还需要注意的是,如果元素里面的内容超出了容器的宽度或者高度,产生了滑动条,那么也不会计算滑动条的部分。
HTML部分如下:

<div class="clientWidth"> 这是clientWidth,永远都不要放弃你的梦想</div>

CSS部分如下:

.clientWidth {
    width: 100px;
    height: 100px;
    margin-top: 10px;
    padding: 15px;
    background-color: #999;
}

JavaScript部分如下:

// 测试clientWidth 属性
let clientWidth = $ele('.clientWidth');
$log('clientWidth的值是:' + clientWidth.clientWidth);

我们会看到控制台的输出如下:

clientWidth的值是:130

这是因为我们的CSS设置元素的宽度为100px,并且元素的padding值为15px;所以这个元素的clientWidth值就是100+15*2=130。接下来我们来测试一下内容超出容器宽度的情况。
CSS部分如下:

.clientWidth {
    /* 以下部分是测试内容超出的情况 */
    white-space: nowrap;
    overflow: scroll;
}

当我们刷新页面,看到控制台的输出依然是:

clientWidth的值是:130

这是因为clientWidth返回的是元素的可见部分的宽度,所以如果里面内容有超出的话,是不会计算超出的这部分内容的宽度的;clientHeight属性和clientWidth属性相似,这里就不在详细讲解了。
需要说明的是,如果上面的情况是在windows的chrome里面的话,那么clientWidth的值会变小,因为产生了垂直的滚动条;所以我们实际看到的clientWidth部分变小了,实际就是原来的值减去垂直滑动条的宽度;所以以后我们在开发的时候也需要多注意这些问题。
下图是我在windows上面的chrome上测试的一部分截图:

关于元素的clientX相关的属性我们暂时就先介绍到这里,接下来我们来介绍DOM元素的offsetX相关的属性。在讲解这些属性之前我们需要知道一个元素的offsetParent表示的是什么,详细的解释可以看这里 HTMLElement.offsetParent ,所谓的offsetParent指的就是包含当前元素的最近的定位元素。如果没有定位的元素,那么就是最近的tabletable cell或者根元素(标准模式下为html;quirks模式下为body)。

我们来测试一下一个元素的offsetParent属性,如下所示:
HTML部分如下:

<div class="testWrapper">
    <div class="test">用来测试test的offsetParent元素</div>
</div>

CSS部分如下:

.testWrapper {
    margin-top: 15px;
    position: relative;
}
// 测试offsetParent 属性
let test = $ele('.test');
let testWrapper = $ele('.testWrapper');
$log('test的offsetParent的值是:', test.offsetParent === testWrapper);

控制台的打印结果如下:

test的offsetParent的值是: true

如果不把testWrapper元素的position属性设置为relative或者absolute的话,那么testoffsetParent属性就不再是testWrapper了。
接下来我们来研究一下offsetLeftoffsetTop这两个属性的值,其中offsetLeft表示当前元素的左上角距离它的offsetParent元素节点的左边界的距离;而offsetTop表示的是当前元素的左上角距离它的offsetParent元素节点的上边界的距离。接下来我们来测试一下:
CSS部分如下:

.testWrapper {
    margin-top: 15px;
    position: relative;
    width: 200px;
    height: 200px;
    padding: 10px;
    background-color: #999999;
    border: 3px solid #333;
}
.test {
    width: 80px;
    height: 80px;
    float: right;
    padding: 5px;
    background-color: #ccff33;
    margin-top: 36px;
    border: 8px solid #333;
}

JavaScript部分如下:

// 测试offsetLeft属性和offsetTop属性
$log('test的offsetLeft的值是:' + test.offsetLeft);
$log('test的offsetTop的值是:' + test.offsetTop);

控制台的输出如下:

test的offsetLeft的值是:104
test的offsetTop的值是:46

因为test是向右浮动的,所以testoffsetLeft的值就是testWrapper的宽度加上左边的padding值,然后减去test的整个宽度(包括borderpadding的值),也就是200 + 10 - (80 + 5 x 2 + 8 x 2) = 104,相应的offsetTop的值就是36 + 10 = 46marginTop的值加上testWrapperpaddingTop的值)。
接下来我们来研究一下offsetWidthoffsetHeight属性,与clientWidthclientHeight不同的是,offsetWidthoffsetHeight不仅包含元素本身CSS设置的宽高,元素内部的padding值;还包含元素的border的值,如果内部内容产生了滑动,还包括垂直滑动条的宽度;我们来测试一下。
HTML部分如下:

<div class="offsetWidth"> 这是offsetWidth,永远都不要放弃你的梦想</div>

CSS部分如下:

.offsetWidth {
    margin-top: 15px;
    width: 100px;
    height: 100px;
    padding: 8px;
    overflow: scroll;
    border: 9px solid black;
}

JavaScript部分如下:

// 测试offsetWidth属性
let offsetWidth = $ele('.offsetWidth');
$log('offsetWidth的值是:' + offsetWidth.offsetWidth);

控制台的输出如下:

offsetWidth的值是:134

这个值是由元素的width值,padding值以及border值相加得来的;也就是100 + 8 x 2 + 9 x 2 = 134。相应的offsetHeight值也是如此,这里就不在演示了。
接下来我们要研究的是scrollX相关的属性,首先是scrollLeft或者scrollTop属性;这两个属性适用于元素内部有滑动的情况,我们来测试一下scrollLeft属性。
HTML部分如下:

<div class="scrollLeft">这是scrollLeft,永远都不要放弃你的梦想</div>

CSS部分如下:

.scrollLeft {
    width: 100px;
    height: 100px;
    margin-top: 15px;
    white-space: nowrap;
    overflow: scroll;
    border: 3px solid  black;
}

JavaScript部分如下:

// 测试scrollLeft属性
let scrollLeft = $ele('.scrollLeft');
$log('scrollLeft的值是:' + scrollLeft.scrollLeft);
// 修改scrollLeft的值
scrollLeft.scrollLeft = 10;
$log('scrollLeft的值是:' + scrollLeft.scrollLeft);

我们可以看到控制台的输出如下:

scrollLeft的值是:0
scrollLeft的值是:10

在刚开始,因为内容没有进行滑动,所以初始值是0,但是当我们改变了scrollLeft的值的时候,我们会发现里面内容向左移动,并且scrollLeft值也变成了我们修改的值。因为scrollTopscrollLeft相似,这里就不再进行演示。
接下来我们来研究一下scrollWidthscrollHeight属性;因为这两个属性比较类似,所以我们就只测试一下scrollWidth属性。我们接着上面的例子进行测试。
JavaScript部分如下:

// 测试scrollWidth属性的值
$log('scrollWidth的值是:' + scrollLeft.scrollWidth);

我们会看到控制台的输出如下:

scrollWidth的值是:294

因为我们的元素本身的宽度只有100 + 3 x 2 = 106,但是控制台却输出了294,这里要说明一下,如果元素内部的内容没有超出元素的宽度,那么scrollWidth的值就是这个元素的clientWidth宽度;如果超出了元素的宽度,那么就是元素内容本身的宽度。
到此为止我们基本上把与一个元素大小有关的属性都讲解了一下,但是需要注意的是,因为操作系统和浏览器的实现的不同,元素的同一个属性值可能会不相同。
还有一些注意的点,我在下表中列了出来,我们来一起看一下:

属性 读写 是否整数
clientLeft 只读 取整(四舍五入)
clientTop 只读 取整(四舍五入)
clientWidth 只读 取整(四舍五入)
clientHeight 只读 取整(四舍五入)
offsetLeft 只读 取整(四舍五入)
offsetTop 只读 取整(四舍五入)
offsetWidth 只读 取整(四舍五入)
offsetHeight 只读 取整(四舍五入)
scrollLeft 读写 取整(四舍五入)
scrollTop 读写 取整(四舍五入)
scrollWidth 只读 取整(四舍五入)
scrollHeight 只读 取整(四舍五入)

文章的在线示例可以在 这里 查看或者查看 源代码 ;如果大家觉得哪里的讲解有问题,也欢迎在下面留言;我们再一起讨论吧。

Comments
Write a Comment