深入理解元素视图的3个方法
前面介绍了offset偏移、client客户区和scroll滚动,这三部分主要从属性的角度来对元素尺寸信息进行获取和修改。本文主要介绍元素视图的三个方法,包括getBoundingClientRect()、getClientRects()和elementFromPoint()
getBoundingClientRect()
判断一个元素的尺寸和位置最简单的方法就是使用getBoundingClientRect()
Element.getBoundingClientRect()方法返回一个对象,该对象提供当前元素节点的大小、它相对于视口(viewport)的位置等信息。但是,各个浏览器返回的对象包含的属性不相同
firefox: top left right bottom width height x y(其中,x=left,y=top) chrome/safari/IE9+:top left right bottom width height IE8-: top left right bottom
问题来了,该方法返回的width和height是客户区宽高client,还是滚动宽高scroll,或者是偏移宽高offset,或者是设置宽高呢
<div id="test" style="width: 100px;height: 100px;padding: 10px;line-height: 200px;border:1px solid black;overflow:scroll">内容</div> <script> //chrome/safari: 220(10+200+10) //firefox/IE: 210(10+200) console.log(test.scrollHeight) //103(100+10+10-17) console.log(test.clientHeight) //122(100+10+10+1+1) console.log(test.offsetHeight) //122(100+10+10+1+1) console.log(test.getBoundingClientRect().height) </script>
由代码结果看出,该方法返回的宽高是偏移宽高offset
Element.getBoundingClientRect().width = border-left-width + padding-left + width + padding-right + border-right-width Element.getBoundingClientRect().height = border-top-width + padding-top + height + padding-bottom + border-bottom-width
下面来分析top、left、right、bottom这四个值
top: 元素顶部相对于视口的纵坐标
left: 元素左边界相对视口的横坐标
right: 元素右边界相对视口的横坐标
bottom:元素底部相对于视口的纵坐标
bottom = top + height right = left + width
[注意]该方法的所有属性值都没有单位,且给定的是元素在页面中相对于视口的位置
问题又来了,相对于视口和相对于页面有什么区别。理论上,与absolute和fixed的区别类似,但表现上与它们正相反。发生滚动时,fixed元素保持不动是为了保持与视口的原始距离;而发生滚动时,getBoundingClientRect()方法的top、left、right、bottom这四个值相应的发生改变,是因为元素位置移动了,与视口距离自然也改变了
bug
IE7-浏览器把视口的左上角坐标设置为(2,2),其他浏览器则将(0,0)作为起点坐标
<body style="margin:0"> <div id="test" style="width: 100px;height: 50px;padding: 10px;line-height: 200px;overflow:scroll;border:1px solid black">内容</div> <script> //chrome/firefox/safari/IE8+ 0 72(50+10+10+1+1) //IE7- 2 74(72+2) console.log(test.getBoundingClientRect().top,test.getBoundingClientRect().bottom) //chrome/firefox/safari/IE8+ 0 122(100+10+10+1+1) //IE71 2 124(122+2) console.log(test.getBoundingClientRect().left,test.getBoundingClientRect().right) </script> </body>
兼容
可以利用IE7-浏览器中特性节点的specified属性实现浏览器识别
function getBoundingClientRect(obj){ var temp = obj.getBoundingClientRect(); //IE7-浏览器 if(Boolean(obj.attributes[0]) && !obj.attributes[0].specified){ return{ left: temp.left -2, top: temp.top -2, right: temp.right -2, bottom: temp.bottom -2 } }else{ return temp; } }
getClientRects()
getClientRects()方法与getBoundingClientRect()不同,该方法是一个返回元素的数个矩形区域的类数组对象。每个类数组对象的参数与getBoundingClientRect()方法相同,每个矩形都有bottom、height、left、right、top和width六个属性,表示它们相对于视口的四个坐标,以及本身的高度和宽度
如果应用于块级元素,则getClientRects()[0]和getBoundingClientRect()的属性返回相同的值,且IE7-浏览器在getClientRects()方法中,同样存在视口左上角坐标被设置为(2,2)的bug
<body style="margin:0"> <div id="test" style="width: 100px;height: 50px;padding: 10px;line-height: 200px;overflow:scroll;border:1px solid black">内容</div> <script> //其他浏览器返回0 0, IE7-浏览器返回 2 2 console.log(test.getClientRects()[0].top,test.getBoundingClientRect().top) //其他浏览器返回0 0, IE7-浏览器返回 2 2 console.log(test.getClientRects()[0].left,test.getBoundingClientRect().left) //72(50+10+10+1+1) 72 console.log(test.getClientRects()[0].height,test.getBoundingClientRect().height) </script>
实际上,该方法主要用于内联元素,内联元素有多少行,该方法返回的对象有多少个成员。这个方法主要用于判断行内元素是否换行,以及行内元素的每一行的位置偏移
<body style="margin:0"> <div style="width:100px;"><span id="el"> Hello World Hello World Hello World </span></div> <script> console.log(el.getClientRects().length); // 3 console.log(el.getClientRects()[0].left); // 0 console.log(el.getClientRects()[0].right); // 88 console.log(el.getClientRects()[0].bottom); // 17 console.log(el.getClientRects()[0].height); // 16 console.log(el.getClientRects()[0].width); // 88 </script> </body>
elementFromPoint()
getBoundingClientRect(x,y)方法使我们能在视口中判定元素的位置。但有时我们想反过来,判定在视口中的指定位置上有什么元素。这可以用Document对象的elementFromPoint()方法来判定。传递X和Y坐标(相对于视口),该方法选择在指定坐标的最上层和最里层的Element对象。如果指定的点在视口以外,elementFromPoint()返回null
[注意]最上层是指z-index最大的元素;最里层是指最里层的子元素
这个方法可以用来检测元素是否发生重叠或是碰撞
<body style="margin:0"> <div id="test" style="width: 100px;height: 100px;"> <span id="span1">123</span> </div> <script> console.log(document.elementFromPoint(2,2).id); //span1 </script>