作为前端工程师,在日常的开发过程中经常会涉及到获取元素尺寸及位置的操作,而 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#targetdiv 元素的 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;
}

记住代码中的 widthheightpaddingmarginborder,这些值对于后文的获取结果非常重要。

得到的效果如下:

initial

注意边缘的白色区域,这是 #container 元素的样式造成的,后文会用到。

接下来,我们将使用这个示例来演示各种属性与方法之间的区别。

clientWidth

获取元素的 clientWidth

let target = document.getElementById("target");

console.log(target.clientWidth);     // 303

得到的值为 303,前文中我们设置的 width300pxborder1px,两者加起来的和应该是 302 才对,为什么多了 1 呢?

如果我们去掉 #target 元素的 overflow

no_overflow

得到的值将是 320。对比前后的效果可以发现,前后 17 的差值其实是滚动条的宽度,和 border 无关。

因此,元素的 clientWidth 是其 width 与左右 padding 的和

值得一提的是,pre 元素在 Firefox 中的高度比在 Chrome 中小:

pre_firefox

两者相差 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、左右 paddingborder 的和。

因此,元素的 offsetWidth 是其 width、左右 paddingborder 宽度的和

offsetHeight

获取元素的 offsetHeight

let target = document.getElementById("target");

console.log(target.offsetHeight);     // 172

得到的值是 172,正好是其 width、上下 paddingborder 的和。

因此,元素的 offsetWidth 是其 width、上下 paddingborder 的和

offsetLeft

获取元素的 offsetLeft

let target = document.getElementById("target");

console.log(target.offsetLeft);     // 65

得到的值是 65,正好是 #target 元素自身的 margin-left#container 元素的 margin-leftpadding-left 的和。

由此可以得出,此时的 #target 元素是相对 body 元素的进行偏移的,我们可以通过获取元素的 offsetParent 来查看其相对偏移对象:

console.log(target.offsetParent);     // <body></body>

得到的结果是 body 元素。

这只是元素默认情况下的偏移量,即相对 body 元素的偏移量。

当元素的某个(或多个)祖先元素的 position 不为 statictransform 属性不为 noneperspective 属性不为 none 时,则获取到的偏移量是相对于最近的符合前述条件的祖先元素的偏移量。

例如,如果我们将 #container 元素的 perspective 设为 10px

#container {
  margin: 20px;
  padding: 30px;
  perspective: 10px
}



 

再次获取元素的 offsetLeft

console.log(target.offsetLeft);     // 45

得到的值是 45,正好是其自身的 margin-left 与父元素 #containerpadding-left 的和。

查看其相对偏移对象:

console.log(target.offsetParent);     // <div id="container"></div>

得到的结果是 #container 元素。

因此,元素的 offsetLeft 有两种情况:

  1. 默认情况下,是其 border-leftbody 元素的 border-left 之间所经过的所有 marginpadding 之和;
  2. 如果其某个(或多个)祖先元素的 position 不为 statictransform 属性不为 noneperspective 属性不为 none,则是其 border-left 到最近的满足前述条件的祖先元素的 border-left 之间所经过的所有 marginpadding 之和。

如果有兄弟元素,则需加上兄弟元素的占位。

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 元素的 margin15px),所以得到的值是 20

如果此时我们再将 #container 元素的 perspective 设为 10px

#container {
  margin: 20px;
  /*padding: 30px;*/
  perspective: 10px;
}



 

再次获取元素的 offsetTop

console.log(target.offsetHeight);     // 0

得到的值是 0,这也是因为发生了 margin 塌陷的原因。

因此,元素的 offsetTop 有三种情况:

  1. 默认情况下,是其 border-topbody 元素的 border-top 之间所经过的所有 marginpadding 之和;
  2. 如果其某个(或多个)祖先元素的 position 不为 statictransform 属性不为 noneperspective 属性不为 none,则是其 border-top 到最近的满足前述条件的祖先元素的 border-top 之间所经过的所有 marginpadding 之和。
  3. 如果发生了 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-leftpre 元素的 margin-left,查看一下效果:

scrollWidth

可以看到,#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,查看一下效果:

scrollHeight

Firefox 上的表现略有不同,得到的值是 574,在边距上,仅包含了 #target 元素的 padding-top

查看一下 Firefox 上的效果:

scrollHeight_firefox

可以明显看到底部边距比顶部边距窄,两者对 premargin 处理是一致的,都包含在内。

因此,元素的 scrollHeight 在计算边距时,Chrome 会包含上下 padding,而 Firefox 仅包含 padding-top

scrollLeft

表现同 scrollWidth

scrollTop

表现同 scrollHeight

getClientRects 与 getBoundingClientRect

两者都可以用于获取元素相对于视口的位置信息和自身尺寸信息,区别在于 getClientRects 返回的是 DOMRectList,而 getBoundingClientRect 返回的是 DOMRect,后者使用更方便一些。

例如:

console.log(target.getClientRects());    
console.log(target.getBoundingClientRect());    

得到的结果如下:

methods

可以看到,两者在内容上是一样的,不过 getClientRects 兼容性差一些,通常我们都选择使用 getBoundingClientRect

结语

获取元素的尺寸及位置在开发中是高频操作,也是比较容易出错的地方,温习一下,总是好的。

最近更新:
作者: MeFelixWang