一、开始
在研究图片懒加载的时候,发现了自己有好几个地方了解的不是很清楚,就是元素的宽高以及位置相关的JS代码,以前只是看过其他大佬的文章以及查MDN,并没有自己完整的做过总结,这里毕竟到这儿了,打算总结一次,供大家参考,我感觉这些还算是比较重要的知识,若有不正确的地方还请多多指正。
二、offsetParent、offsetLeft、offsetTop、offsetWidth、offsetHeight
- 注意:所有测试都是基于Chrome 78浏览器来测试的,手头设备有限,不能一一测试,其它非Chrome的测试总结均来源于网络。
2.1 offsetParent
首先测试offsetParent,这个返回的是一个DOM元素,而不是一个数值。先来看看MDN上的中文描述:HTMLElement.offsetParent 是一个只读属性,返回一个指向最近的(closest,指包含层级上的最近)包含该元素的定位元素。如果没有定位的元素,则 offsetParent 为最近的 table, td, th或body元素。当元素的 style.display 设置为 "none" 时,offsetParent 返回 null。那么现在开始测试,这儿先贴上我的测试代码:
* {
margin: 0;
padding: 0;
}
.inner::before {
content: '';
display: table;
}
.inner {
position: relative;
height: 500px;
}
.content {
margin: 16px 0 0 20px;
}
<div id="outter" class="outter">
<div id="inner" class="inner">
<div id="nope" class="nope">
<p id="content" class="content">内容:测试数据值</p>
</div>
</div>
</div>
let elm = document.getElementById('content');
console.log(elm.offsetParent);
首先我给inner测试,我给它设置了定位,然后查看JS的输出发现输出的inner及其子元素。当我取消给inner设置定位,直接输出了整个body。我再给inner尝试了absolute,fixed,static定位,除开static返回body,其它都返回inner。一句话:offsetParent返回的是离content的最近的设置了除static定位的父级元素,否则返回body。
2.2 offsetWidth、offsetHeight
接下来我修改了content的CSS代码,如下:
.content {
margin: 16px 0 0 20px;
width: 200px;
height: 200px;
padding: 10px;
border: 2px solid gold;
}
let elm = document.getElementById('content');
console.log(elm.offsetWidth);
console.log(elm.offsetHeight);
然后查看了JS的输出为:224 224 这里得出一个结论公式:offsetWidth = content + padding + border;与margin外边距无关,offsetHeight同理,及时content元素包含滚动条,也不影响这个公式计算。
2.3 offsetLeft、offsetTop
- 注意:这儿都是建立在有offsetPartent的情况下。也就是我的inner设置了定位relative,不然情况肯定不是这样
到这儿,我们继续测试这两个属性的值,首先代码我做了些调整,如下:
* {
margin: 0;
padding: 0;
}
.outter::before {
content: '';
display: table;
}
.inner::before {
content: '';
display: table;
}
.inner {
position: relative;
width: 500px;
height: 500px;
padding: 20px;
border: 4px solid hotpink;
margin: 40px;
}
.content {
width: 200px;
height: 200px;
padding: 10px;
border: 2px solid gold;
margin: 20px;
}
<div id="outter" class="outter">
<div id="inner" class="inner">
<p id="content" class="content">内容:测试数据值</p>
</div>
</div>
let elm = document.getElementById('content');
console.log(elm.offsetLeft);
console.log(elm.offsetTop);
然后打印一下,JS输出的值为:40 40;这个是最正常的情况下我们得出的结论,也就是满足: offsetLeft = inner.paddingLeft + content.marginLeft = 20 + 20 = 40px; offsetTop = inner.paddingTop + content.marginTop = 20 + 20 = 40px;我修改一下CSS代码:
.content {
position: relative;
left: 10px;
top: 10px;
width: 200px;
height: 200px;
padding: 10px;
border: 2px solid gold;
margin: 20px;
}
这个时候公式就变成了: offsetLeft = inner.paddingLeft + content.marginLeft + content.left= 20 + 20 + 10 = 50px; offsetTop = inner.paddingTop + content.marginTop + content.top = 20 + 20 + 10 = 50px; 如果我在content元素外加了个父元素parent,并且parent未设置定位,像下面的代码:
<div id="outter" class="outter">
<div id="inner" class="inner">
<div id="parent" class="parent">
<p id="content" class="content">内容:测试数据值</p>
</div>
</div>
</div>
那么此时的offsetLeft和offsetTop的值要看parent了,对于offsetLeft来说,只要parent的paddingLeft、borderLeft、marginLeft不为0,就都要计算加入offsetLeft,offsetTop同理。
contnet元素脱离文档流情况
.content {
position: absolute;
left: 10px;
top: 10px;
width: 200px;
height: 200px;
padding: 10px;
border: 2px solid gold;
margin: 20px;
}
子元素content变成绝对定位,脱离了文档流,这个时候公式变成了: offsetLeft = content.marginLeft + content.left= 20 + 10 = 30px; offsetTop = content.marginTop + content.top = 20 + 10 = 30px; 由于fixed定位会造成offsetParent为null,这里不讨论;但这儿我测试了一下,offsetParent虽然为null,但是offsetLeft和offsetTop还是有值,都为30。 所以脱离文档流的元素的offsetLeft和offsetTop可以总结为: offsetLeft = content.left + content.marginLeft offsetTop = content.top + content.marginTop
三、clientHeight、clientWidth、clientLeft、clientTop
3.1、clientWidth、clientHeight
先来看看MDN的描述:内联元素以及没有 CSS 样式的元素的 clientWidth 属性值为 0。Element.clientWidth 属性表示元素的内部宽度,以像素计。该属性包括内边距,但不包括垂直滚动条(如果有)、边框和外边距。 这儿我测试了内联元素的clientWidth和clientHeight确实没有值,没有CSS样式那个我测试了还是有值,不知道这个所谓的无CSS样式是怎么定义的,我测试就是写个标签加了内容,没有任何附加样式。好了接下来开始测试。 这儿我还是先单独贴一下我的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.outter::before {
content: '';
display: table;
}
.inner::before {
content: '';
display: table;
}
.inner {
position: relative;
width: 500px;
height: 500px;
padding: 20px;
border: 4px solid hotpink;
margin: 40px;
}
.content {
width: 200px;
height: 200px;
padding: 10px;
border: 2px solid gold;
margin: 20px;
}
</style>
</head>
<body>
<div id="outter" class="outter">
<div id="inner" class="inner">
<p id="content" class="content">内容:测试数据值</p>
</div>
</div>
<script>
let elm = document.getElementById('content');
console.log(elm.clientWidth);
console.log(elm.clientHeight);
</script>
</body>
</html>
这个是常规状态,然后JS处输出的值为:220 220。所以可以总结出一个公式: clientWidth = content + padding,跟offsetWidth区别就是没算边框border,如果当竖直方向出现滚动条,那么还要减去滚动条的宽度才是clientWidth,这个跟offsetWidth有区别,offsetWidth不需要考虑滚动条。clientHeight与clientWidth同理。
3.2 clientLeft、clientTop
MDN描述先走一波:表示一个元素的左边框的宽度,以像素表示。如果元素的文本方向是从右向左(RTL, right-to-left),并且由于内容溢出导致左边出现了一个垂直滚动条,则该属性包括滚动条的宽度。clientLeft 不包括左外边距和左内边距。clientLeft 是只读的。 我修改一下JS代码,看看输出情况:
let elm = document.getElementById('content');
console.log(elm.clientLeft);
console.log(elm.clientTop);
结果输出:2 2。看来没问题。
四、scrollWidth、scrollHeight、scrollLeft、scrollTop
4.1 scrollWidth、scrollHeight
MDN上面这样描述的:Element.scrollWidth 这个只读属性是元素内容宽度的一种度量,包括由于overflow溢出而在屏幕上不可见的内容。scrollWidth值等于元素在不使用水平滚动条的情况下适合视口中的所有内容所需的最小宽度。 宽度的测量方式与clientWidth相同:它包含元素的内边距,但不包括边框,外边距或垂直滚动条(如果存在)。 它还可以包括伪元素的宽度,例如::before或::after。 如果元素的内容可以适合而不需要水平滚动条,则其scrollWidth等于clientWidth
这几个属性算是开发里面出场比较高的了。首先来说说body吧。一般来说我们不会刻意去设置body的宽高,所以这样来讨论,一般宽度就是你浏览器可视区多宽,body就是多宽,高度的话根据body里面的内容多少来定,里面内容有多少就会把body撑起来多高。我们这儿还是来讨论div的情况。
无滚动条的情况,修改JS代码:
let elm = document.getElementById('content');
console.log(elm.scrollWidth);
console.log(elm.scrollHeight);
发现输出:220 220,输出的值跟clientWidth和clientHeight一样。 接下来修改代码,隐藏部分内容来看看:
.content {
width: 200px;
height: 200px;
padding: 10px;
border: 2px solid gold;
margin: 20px;
overflow: scroll;
white-space: nowrap;
}
首先是单行显示,我发现scrollWidth不在是220了,变成了1703,说明这个scroll在出现滚动条的时候,宽度是实际的内容宽度,哪怕它隐藏掉了也要算,去掉white-space: nowrap;,看看竖直方向的情况发现情况一样。
4.2 scrollLeft、scrollTop
这两个属性比较特殊,前面的都是只读属性,这个两个属性可读可写,使用频率很高,还是上面的代码不变,我们直接打印输出:
let elm = document.getElementById('content');
console.log(elm.scrollLeft);
console.log(elm.scrollTop);
发现全部是是0,这儿肯定是0了,当我把内容水平滚动一部分,然后再chrome的控制台打印输出elm.scrollLeft,发现输出的值变为了673,说明水平方向有了滚动值,同理对scrollTop也适用。
五、getBoundingClientRect()
这个返回元素的大小及其相对于视口的位置,并且它返回的值可以包含小数。getBoundingClientRect用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置,还可以计算出元素的宽高。 演示如下图,来自MDN:

这儿我使用Chrome浏览器打印输出了getBoundingClientRect()的返回值。如下图:

这儿还是贴出完整的演示代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.outter::before {
content: '';
display: table;
}
.inner::before {
content: '';
display: table;
}
.content {
width: 200px;
height: 200px;
padding: 10px;
border: 2px solid gold;
margin: 20px;
overflow: scroll;
}
</style>
</head>
<body id="bb">
<div id="outter" class="outter">
<div id="inner" class="inner">
<p id="content" class="content">内容:测试数据值
Lorem ipsum dolor sit amet consectetur adipisicing elit. Error soluta rerum quidem aspernatur alias et adipisci omnis enim hic fuga saepe culpa voluptate eos suscipit, nemo quis. Velit, accusantium unde.
</p>
</div>
</div>
<script>
let elm = document.getElementById('content');
console.log(elm.getBoundingClientRect());
</script>
</body>
</html>
这个函数返回的对象的width和height只会计算元素的conten和padding和border,不会计算外边距。
六、 window.scrollTo(x, y)、window.scrollBy(x, y)、window.screenX、window.screenY
这几个方法和属性还算常用,我在这儿也总结一下。
- scrollTo()是绝对位置滚动,每次都是相对初始位置(0, 0)位置滚动。
- scrollBy()是相对位置滚动,相对于上次移动的最后位置移动。
- window.scrollX是浏览器窗口左边到屏幕左边的距离。
- window.scrollY是浏览器窗口顶部到屏幕顶部的距离。
- window.scroll()效果跟scrollTo效果相同。
这里做几个补充: 1、关于screenX和screenY这里贴一张图供大家理解,如下图

2、document.documentElement.scrollTop与document.body.scrollTop document.documentElement表示获取到html,document.body表示获取到body。当页面声明了DOCTYPE(DTD),使用document.body.scrollTop获取不到页面滚动的距离,就必须使用document.documentElement.scrollTop来获取。所以一般我们获取滚动距离的兼容代码最好写成这样:
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
3、关于绑定滚动事件scroll 这里我试了Chrome,只有window和document两个能绑定scroll滚动事件,document.documentElement和document.body绑定不起,这个应该跟浏览器内核有关系。
4、ios safari的document.documentElement无法使用scrollTo() chrome浏览器上没有问题,ios safari失败,使用window的话都可以,而且ios safari上的document.documentElement.scrollTop能取到值但是不能赋值。
七、JS Event中的offsetX、clientX、pageX、screenX、offsetY、clientY、pageY、screenY
这几个属性的值我用一张图来概括:

注意:这几个值有些是在实验阶段,兼容性有待考察,建议大家在使用时注意查阅MDN和多测试,这儿限于条件原因,不能一一测试。同时我上面的这张图是基于MouseEvent来总结的。
八、最后
总结完毕,若有错误还请大家多多指正。