作为前端工程师,在日常的开发过程中经常会涉及到获取元素尺寸及位置的操作,而 DOM 为我们提供了好几种属性及方法,每种属性及方法获取到的结果都不太一样,因此,搞清楚它们之间的区别是能够正确使用的前提。
这篇文章就来温习一下这些属性及方法。
假设有如下 HTML 结构:
<div id="container">
<div id="target">
<pre>
width: 300px;
height: 150px;
padding: 10px;
margin: 15px;
border: solid 1px black;
background-color: deepskyblue;
color: white;
font-weight: bold;
overflow: auto;
</pre>
</div>
</div>
<pre>
标签中显示的是 id
为 #target
的 div
元素的 CSS 代码,方便阅读。
对应的 CSS 代码如下:
html, body {
margin: 0;
padding: 0;
}
#container {
margin: 20px;
padding: 30px;
}
#target {
width: 300px;
height: 150px;
padding: 10px;
margin: 15px;
border: solid 1px black;
background-color: deepskyblue;
color: white;
font-weight: bold;
overflow: auto;
}
pre {
margin: 0;
background-color: orange;
}
记住代码中的 width
、height
、padding
、margin
和 border
,这些值对于后文的获取结果非常重要。
得到的效果如下:
注意边缘的白色区域,这是 #container
元素的样式造成的,后文会用到。
接下来,我们将使用这个示例来演示各种属性与方法之间的区别。
clientWidth
获取元素的 clientWidth
:
let target = document.getElementById("target");
console.log(target.clientWidth); // 303
得到的值为 303
,前文中我们设置的 width
为 300px
,border
为 1px
,两者加起来的和应该是 302
才对,为什么多了 1
呢?
如果我们去掉 #target
元素的 overflow
:
得到的值将是 320
。对比前后的效果可以发现,前后 17
的差值其实是滚动条的宽度,和 border
无关。
因此,元素的 clientWidth
是其 width
与左右 padding
的和。
值得一提的是,pre
元素在 Firefox 中的高度比在 Chrome 中小:
两者相差 30px
。
clientHeight
获取元素的 clientHeight
:
let target = document.getElementById("target");
console.log(target.clientHeight); // 170
得到的值是 170
。
因此,元素的 clientHeight
是其 height
与上下 padding
的和。
clientLeft
获取元素的 clientLeft
:
let target = document.getElementById("target");
console.log(target.clientLeft); // 1
得到的值是 1
,正好是左边 border
的宽度。
因此,元素的 clientLeft
是其 border-left
的宽度。
clientTop
获取元素的 clientTop
:
let target = document.getElementById("target");
console.log(target.clientTop); // 1
得到的值是 1
,正好是上边 border
的宽度(前文中我们设置的 border
是针对所有方向的)。
因此,元素的 clientLeft
是其 border-top
的宽度。
offsetWidth
获取元素的 offsetWidth
:
let target = document.getElementById("target");
console.log(target.offsetWidth); // 322
得到的值是 322
,正好是其 width
、左右 padding
与 border
的和。
因此,元素的 offsetWidth
是其 width
、左右 padding
与 border
宽度的和。
offsetHeight
获取元素的 offsetHeight
:
let target = document.getElementById("target");
console.log(target.offsetHeight); // 172
得到的值是 172
,正好是其 width
、上下 padding
与 border
的和。
因此,元素的 offsetWidth
是其 width
、上下 padding
与 border
的和。
offsetLeft
获取元素的 offsetLeft
:
let target = document.getElementById("target");
console.log(target.offsetLeft); // 65
得到的值是 65
,正好是 #target
元素自身的 margin-left
与 #container
元素的 margin-left
和 padding-left
的和。
由此可以得出,此时的 #target
元素是相对 body
元素的进行偏移的,我们可以通过获取元素的 offsetParent
来查看其相对偏移对象:
console.log(target.offsetParent); // <body></body>
得到的结果是 body
元素。
这只是元素默认情况下的偏移量,即相对 body
元素的偏移量。
当元素的某个(或多个)祖先元素的 position
不为 static
或 transform
属性不为 none
或 perspective
属性不为 none
时,则获取到的偏移量是相对于最近的符合前述条件的祖先元素的偏移量。
例如,如果我们将 #container
元素的 perspective
设为 10px
:
#container {
margin: 20px;
padding: 30px;
perspective: 10px
}
再次获取元素的 offsetLeft
:
console.log(target.offsetLeft); // 45
得到的值是 45
,正好是其自身的 margin-left
与父元素 #container
的 padding-left
的和。
查看其相对偏移对象:
console.log(target.offsetParent); // <div id="container"></div>
得到的结果是 #container
元素。
因此,元素的 offsetLeft
有两种情况:
- 默认情况下,是其
border-left
到body
元素的border-left
之间所经过的所有margin
与padding
之和; - 如果其某个(或多个)祖先元素的
position
不为static
或transform
属性不为none
或perspective
属性不为none
,则是其border-left
到最近的满足前述条件的祖先元素的border-left
之间所经过的所有margin
与padding
之和。
如果有兄弟元素,则需加上兄弟元素的占位。
offsetTop
获取元素的 offsetTop
:
let target = document.getElementById("target");
console.log(target.offsetHeight); // 65
得到的值是 65
。
元素的 offsetTop
值和 offsetLeft
一样,也有上述的两种情况,除此之外,它还会受到 margin
的影响。
当发生 margin
塌陷时,计算元素的 offsetTop
时所采用的 margin
会以发生塌陷的两个 margin
中最大的值(不确定是 margin-top
还是 margin-bottom
,根据实际情况分析)为准。
例如,如果我们去掉 #container
元素的 padding
:
#container {
margin: 20px;
/*padding: 30px;*/
}
再次获取元素的 offsetTop
:
console.log(target.offsetHeight); // 20
得到的值是 20
,因为 #target
元素和 #container
元素发生了 margin
塌陷,而两者之间最大的 margin
是 #container
元素的 20px
(#target
元素的 margin
为 15px
),所以得到的值是 20
。
如果此时我们再将 #container
元素的 perspective
设为 10px
:
#container {
margin: 20px;
/*padding: 30px;*/
perspective: 10px;
}
再次获取元素的 offsetTop
:
console.log(target.offsetHeight); // 0
得到的值是 0
,这也是因为发生了 margin
塌陷的原因。
因此,元素的 offsetTop
有三种情况:
- 默认情况下,是其
border-top
到body
元素的border-top
之间所经过的所有margin
与padding
之和; - 如果其某个(或多个)祖先元素的
position
不为static
或transform
属性不为none
或perspective
属性不为none
,则是其border-top
到最近的满足前述条件的祖先元素的border-top
之间所经过的所有margin
与padding
之和。 - 如果发生了
margin
塌陷,则以两者之间最大的margin
值为准。
如果有兄弟元素,则需加上兄弟元素的占位。
scrollWidth
我们先修改一下 pre
元素的 CSS 代码,增加它的宽度:
pre {
margin: 0 20px;
background-color: orange;
width: 500px;
border: solid 2px black;
padding: 10px;
}
然后获取元素的 scrollWidth
:
let target = document.getElementById("target");
console.log(target.scrollWidth) // 554
得到的值是 554
,在边距上,仅包含了 #target
元素的 padding-left
和 pre
元素的 margin-left
,查看一下效果:
可以看到,#target
元素的 padding-right
被忽略了,Firefox 上的表现也是一样的。
因此,元素的 scrollWidth
在计算边距时,仅包含左侧经过的 margin
和 padding
。
scrollHeight
我们先修改一下 pre
元素的 CSS 代码,增加它的高度:
pre {
margin: 20px 0;
background-color: orange;
height: 500px;
border: solid 2px black;
padding: 10px;
}
然后获取元素的 scrollHeight
:
let target = document.getElementById("target");
console.log(target.scrollHeight) // 584
得到的值是 584
,在边距上,包含了 #target
元素的上下 padding
,查看一下效果:
Firefox 上的表现略有不同,得到的值是 574
,在边距上,仅包含了 #target
元素的 padding-top
。
查看一下 Firefox 上的效果:
可以明显看到底部边距比顶部边距窄,两者对 pre
的 margin
处理是一致的,都包含在内。
因此,元素的 scrollHeight
在计算边距时,Chrome 会包含上下 padding
,而 Firefox 仅包含 padding-top
。
scrollLeft
表现同 scrollWidth
。
scrollTop
表现同 scrollHeight
。
getClientRects 与 getBoundingClientRect
两者都可以用于获取元素相对于视口的位置信息和自身尺寸信息,区别在于 getClientRects
返回的是 DOMRectList,而 getBoundingClientRect
返回的是 DOMRect,后者使用更方便一些。
例如:
console.log(target.getClientRects());
console.log(target.getBoundingClientRect());
得到的结果如下:
可以看到,两者在内容上是一样的,不过 getClientRects
兼容性差一些,通常我们都选择使用 getBoundingClientRect
。
结语
获取元素的尺寸及位置在开发中是高频操作,也是比较容易出错的地方,温习一下,总是好的。