问题1:offsetTop,offsetLeft,offsetWidth,offsetHeight等方法的用法?
对于offsetWidth/offsetHeight/offsetLeft/offsetTop的计算都是以border为界限的,也就说从子元素border开始计算,到父元素border为止!对于offsetTop/offsetLeft都是要牵涉到offsetParent的,该元素是非static定位的父元素(可以阅读 无定位父元素时offsetParent为body,但是offsetTop计算距离从html开始)。同时,对于offsetTop来说,还有一个重要的知识点,那就是垂直方向上会发生margin重叠
<div style="width:200px;height:200px;border:1px solid green;padding:10px;margin:10px;" id="outer"> <div style="width:100px;margin:10px;height:100px;border:1px solid red;overflow:scroll;" id="div"> </div> </div>JS部分如下:
//offsetWidth/offsetHeight不包括margin,只是包括border+padding+width但是包含了滚动条! var dom=document.querySelector("#div"); //打印102 console.log(dom.offsetWidth); //打印102 console.log(dom.offsetHeight); //要计算offsetLeft必须要知道offsetParent元素,根据jQuery的offsetParent方法我们知道,它对应于非static的父元素 //只要不是static定位都是可以的,而且是最近的!所以这个元素的offsetParent当前是body元素,所以结果是: //39px=10px(自己的margin)+10px(父元素的outer的padding)+1px(父元素的border-left)+10px(父元素body的margin)+8px(body的margin)= console.log(dom.offsetLeft); //这时候不要以为offsetTop也是和offsetLeft一样计算,因为这时候会出现垂直方向上margin的重叠现象,从而为下面的值 //31px=10px(自己的marign)+10px(父元素的padding)+1px(父元素的boder)+10px(因为body没有border或者padding所以垂直方向上outer和body的margin重叠,最后得到10px,也就是outer的margin是10px了!) //总之:offsetTop是考虑了margin重叠后的结果的值! console.log(dom.offsetTop); //但是如果把outer元素设置为任何一个非static的定位,这时候offsetLeft就会是下面的结果 //20px=10px(自己的margin)+10px(父元素的padding)
问题2:我们知道jQuery中也有一个offset方法获取元素相对文档的位移量,那么这个方法获取到的值和offsetLeft/offsetTop有什么关系?
还是以该HTML为例:
<div style="width:200px;height:200px;border:1px solid green;padding:10px;margin:10px;" id="outer"> <div style="width:100px;margin:10px;height:100px;border:1px solid red;overflow:scroll;" id="div"> </div> </div>JS部分:
console.log($('#div').offset()); //打印Object {top: 31, left: 39} //其中offset表示获取该元素到文档的距离,如果元素的offsetParent是body那么 //offset获取到值就是offsetTop/offsetLeft获取到的值! var dom=document.querySelector("#div"); //打印31px console.log(dom.offsetTop); //打印39px console.log(dom.offsetLeft);
这时候我们奇怪的发现offset获取到的值和offsetTop/offsetLeft是相等的,难道jQuery加入这两个方法是简单为了替换offsetTop/offsetLeft?其实并非如此,jQuery这个方法是获取当前元素到文档的距离,是文档( 获取元素相对于文档的偏移量以及元素相对于父元素的偏移量一文明确给出了如何计算到文档的距离),但是offsetLeft/offsetTop是获取到offsetParent的距离。至于这个例子的offsetTop值的输出要弄清楚以下两点:
(1)当前元素没有一个父元素有非static的定位,这时候offsetParent就是body,但是计算offsetTop值是参照html元素的,可以参考无定位父元素时offsetParent为body,但是offsetTop计算距离从html开始!
(2) body元素和html元素之间的一些表现一文中表明,html元素可以在垂直方向上移动,也就是说html元素可以设置marginTop!
我们举一个例子:
<div style="width:200px;height:200px;border:1px solid green;padding:10px;margin:10px;" id="outer"> <div style="width:100px;margin:10px;height:100px;border:1px solid red;overflow:scroll;" id="div"> </div> </div>我们给body元素一个marginTop为100px
body { background-color:#ccc; margin-top:100px; } html { background-color:red; }这时候body元素在html基础上发生了移动100px,但是我们通过offsetLeft获取相对于body的偏移量的时候(上面说过是从html开始计算offsetLeft/offsetTop的值)和html元素是没有关系的,所以对于#div元素最后的offsetLeft值应该是10px(自己的margin)+111px(#outer元素的padding,border,以及垂直方向上margin重叠后的100px)=121px!
var dom=document.querySelector('#div'); //获取到文档的距离,getBoundingClientRect返回也是121px,也就是相对于视口的偏移量是121px //同时文档滚动距离是0,而且documentElement的clientTop是0! var domDis=$(dom).offset().top; console.log(domDis); //不过元素div距离body元素,也就是offsetParent的距离是121px! var result=dom.offsetTop; console.log(result);
那么我们如果给html元素一个marginTop结果会怎么样?
body { background-color:#ccc; } html { background-color:red; margin-top:100px; }html结构不变,但是css部分的marginTop设置给html元素的,这时候html元素相对于浏览器可视区域的垂直方向的移动是100px。我们依然通过同样的js去测试元素到达html元素的距离和到达文档顶部的距离是如何的
var dom=document.querySelector('#div'); //打印131 var domDis=$(dom).offset().top; console.log(domDis); //打印31 var result=dom.offsetTop; console.log(result);给html设置了margin-top以后,我们明显的发现到文档的距离(不要纠结什么是文档,只要知道怎么计算的,在这里文档成了可视区域顶部了getBoudingClientRect计算)和到html(offsetLeft/offsetTop)的距离有差别!
垂直方向上发生margin重叠表现是什么?
<div style="width:200px;height:200px;border:1px solid green;padding:10px;margin:20px;" id="outer"> <div style="width:100px;margin:10px;height:100px;border:1px solid red;overflow:scroll;" id="div"> </div> </div>这时候我们获取html元素和body元素到可视区域的距离:
var dom=document.querySelector("html"); console.log(dom.getBoundingClientRect().top);//打印0 var dom1=document.querySelector("body"); console.log(dom1.getBoundingClientRect().top);//打印20通过我们测试的结果后发现:如果发生了垂直方向上margin重叠的情况了,那么body就会在html元素的基础上发生移动!例如:在这里例子中,对于body元素来说其margin-top为8px,但是outer元素和它发生了margin重叠的情况,最后就是body元素的margin也变成了20px,即outer和body元素发生了重叠,然后整体在html基础上移动了20px!因此,对于body来说它在可视区域中的位移就是20px。那么outer呢,其在可视区域的移动的位移是多少?
var dom=document.querySelector("#outer"); console.log(dom.getBoundingClientRect().top);//打印20px通过测试结果我们知道outer和body发生垂直margin重叠后,其相对于可视区域的的位移是完全相等的(因为body元素没有border,所以两者完全重合,但是反过来想想,如果有border那么两者就不会发生margin重叠了,所以说 发生margin重叠后的父子元素肯定会完全重合,也就是相对于可视区域的距离是相等的),通过 该图你也会看到outer和body在html基础上发生了移动,而且是同时移动的,所以距离html元素的距离都是20px! 问题3:既然讲到了jQuery的 offset方法,那么我们就有必要知道他是如何计算到文档的距离的,这时候就会引入getBoundingClientRect方法?
var dom=document.querySelector("#div"); var result=dom.getBoundingClientRect(); //是一个对象,其中left是39,top是31! console.log(result);通过 该图,我们知道getBoundingClientRect获取到的是元素距离可视区域的位移:
(1)offsetLeft/offsetTop是获取到offsetParent的位移(如果没有父元素的被定位就是获取到html位移),而jQuery的offset方法通过getBoundingClientRect实现的是到文档的距离。
(2)getBoundingClientRect是获取元素到视口的距离,之所以上面通过getBoundingClientRect和通过offset获取到的值相同是因为没有出现滚动条,同时也没有为html设置marginTop等,所以视口和文档是一致的。
(3)getBoundingClientRect获取视口距离的时候具有兼容性问题,IE7浏览器(标准模式)认为文档的左上角是(2,2),而其它浏览器是(0,0),我们看看jQuery是如何做兼容的:
if ( typeof elem.getBoundingClientRect !== strundefined ) { box = elem.getBoundingClientRect(); } //获取window对象 win = getWindow( doc ); return { top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),//要加上滚动距离和文档兼容的2像素! left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 ) };
总之:到文档的距离=滚动距离+元素相对视口的位移
问题4:我们都知道jQuery的offset是获取到文档的距离,但是jQuery还有一个position方法获取到被定位的祖先元素的距离啊,这不就是offsetLeft/offsetTop的值?
解答:错误
console.log($('#div').position()); //打印Object {top: 10, left: 10} var dom=document.querySelector("#div"); //打印20px console.log(dom.offsetTop); //打印20px console.log(dom.offsetLeft);很显然通过position方法获取到距离和offsetLeft/offsetTop是不一样的。而且 该图清晰的解释了jQuery的position方法获取到的值和offsetLeft/offsetTop的区别,
按照jQuery的公式:子元素到文档距离-父元素到文档距离-父元素的borderTop-子元素的margin!那么很显然,position方法是从子元素的margin往外面计算的,但是offsetLeft/offsetTop是从border往外面计算的。所以通过offsetTop/offsetLeft获取到的值比position多了子元素的margin值,但是两者都是相对于被定位元素计算距离的!
问题5:有一个常用的方法获取一个元素到文档左边的距离,但是这个方法有没有问题?解答:有问题
function getElementLeft(elem) { var actualLeft=elem.offsetLeft; //获取该元素到父元素的距离,从border开始计算 var current=elem.offsetParent; while(current!==null) { actualLeft+=current.offsetLeft; current=current.offsetParent; } return actualLeft; }我们看一下下面这种情况:
<div style="width:200px;height:200px;border:1px solid green;padding:10px;margin:10px;position:relative;" id="outer"> <div style="width:100px;margin:10px;height:100px;border:1px solid red;overflow:scroll;" id="div"> </div> </div>JS部分:
$(document).ready(function() { var result=getElementLeft($('#div')[0]); //打印38px=20px(#div的margin是10px,#outer的padding是10px)+10px(#outer的margin)+8px(body的margin!) console.log(result); //打印Object {top: 31, left: 39} console.log($('#div').offset()); })也就是说,通过这种方式来完成到文档的左边距离计算是有问题的,问题在于: 计算的时候只是考虑了margin,所以他忽略了所有父元素的border!
function getElementTop(elem) { var actualTop=elem.offsetTop; console.log(actualTop); var current=elem.offsetParent; while(current!==null) { actualTop+=current.offsetTop; current=current.offsetParent; } <span style="white-space:pre"> </span> return actualTop; }我们针对下面的HTML(注意:有一个很大的border值)
<div style="width:200px;height:200px;border:100px solid green;padding:10px;margin:10px;position:relative;" id="outer"> <div style="width:100px;margin:10px;height:100px;border:1px solid red;overflow:scroll;" id="div"> </div> </div>JS部分:
$(document).ready(function() { var result=getElementTop($('#div')[0]); //打印30px=20px(#div的margin和#outer的padding)+10px(#outer相对于body的移动值) console.log(result); //打印Object {top: 130, left: 138},所以上面这种方式忽略了父元素的border //同时也没有考虑body本身的相对于文档的值,因为到body就停止了! <span style="white-space:pre"> </span> console.log($('#div').offset()); })这时候这种方法获取到元素距离文档的偏移量误差就会非常大,原因在于: 忽略了父元素的border,同时也没有考虑到垂直方向上面的margin重叠情况!
解答:
不同点:
(1)client类型是不包含border的,但是offset是包含border的
(2)client类型是不包含滚动条的,因为滚动条不能用作内容的显示
相同的:
offset和client都是只读的,同时每次访问都是要重新计算的(可能导致页面回流)。
<div style="width:200px;height:200px;border:1px solid green;padding:10px;margin:10px;position:relative;" id="outer"> <div style="width:100px;margin:10px;height:100px;border:1px solid red;padding:10px;overflow:scroll;" id="div"> </div> </div>
JS部分:
$(document).ready(function() { var dom=document.querySelector("#div"); console.log(dom.clientWidth); console.log(dom.offsetWidth); //offsetWidth/offsetHeight都是122px=100(width)+20px(padding)+2px(border)! //所以其是包含了滚动条了,也就是说滚动条宽度是包含在offset值中的! //但是clientHeight/clientWidth都是103px,所以滚动条在chrome47中宽度是122-2(client不包含border) //-103=17px, console.log(dom.clientHeight); console.log(dom.offsetHeight); })clientHeight/clientWidth常用于获取浏览器视口的大小
function getViewPort() { if(document.compatMode=='BackCompat')//兼容模式 { return { width:document.body.clientWidth, height:document.body.clientHeight } }else//标准模式 { return{ width:document.documentElement.clientWidth, height:document.documentElement.clientHeight } } }问题7:scrollHeight/scrollWidth/scrollLeft/scrollTop等的用法有那些?
scrollTop用于设置或返回当前匹配元素相对于垂直滚动条顶部的偏移。也就是该元素在可见区域之上被隐藏部分的高度(单位:像素)。如果垂直滚动条在最上面(也就是可见区域之上没有被隐藏的内容),或者当前元素是不可垂直滚动的,那么scrollTop将返回0。
上次看lazyload源码的时候看到了scrollTop的用法,其用于判断元素是否在可视区域之下:
scrollTop在这里用于判断元素是否在可视区域之下:
$.belowthefold = function(element, settings) { var fold; if (settings.container === undefined || settings.container === window) { //scrollTop用于获取到元素已经滚动进去的距离! fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop(); } else { fold = $(settings.container).offset().top + $(settings.container).height(); } //如果返回true表示元素在可视区域之下,如果是false表示元素在可视区域之内! return fold <= $(element).offset().top - settings.threshold; };scrollTop在这用于判断元素是否滚动过多被隐藏了:
$.abovethetop = function(element, settings) { var fold; //如果是window对象那么获取在垂直方向上面滚动的距离 if (settings.container === undefined || settings.container === window) { fold = $window.scrollTop(); } else { fold = $(settings.container).offset().top; } //如果滚动条太多,那么这时候就会被隐藏,返回true表示已经滚动隐藏了,不在可视区域内了! return fold >= $(element).offset().top + settings.threshold + $(element).height(); };那么scrollLeft表示被隐藏在内容区域左侧的像素数,通过设置这个值可以获取元素滚动的位置,下面这个代码来自于lazyload.js用于判断元素是否由于水平滚动过多而隐藏
$.leftofbegin = function(element, settings) { var fold; //如果是window对象那么我们获取已经在水平方向上滚动的距离 if (settings.container === undefined || settings.container === window) { fold = $window.scrollLeft(); } else { fold = $(settings.container).offset().left; } //如果元素滚动的距离已经大于元素距离文档左边的距离,那么表示元素在水平方向上已经被隐藏了 //既然是隐藏了,那么我们又不需要做任何处理了! return fold >= $(element).offset().left + settings.threshold + $(element).width(); };下面用了scrollLeft判断元素是否在水平方向上还没有出现在可视区域之内
$.rightoffold = function(element, settings) { var fold; //如果container是window那么获取窗口的滚动距离+窗口的宽度! if (settings.container === undefined || settings.container === window) { fold = $window.width() + $window.scrollLeft(); } else { fold = $(settings.container).offset().left + $(settings.container).width(); } //返回true表示元素还没有出现,返回false表示元素出现在可视区域之内了! return fold <= $(element).offset().left - settings.threshold; };scrollWidth/scrollHeight主要用于确定内容的实际大小,带有滚动条的页面总高度就是document.documentElement.scrollHeight。对于 不包含滚动条的页面而言,scrollWidth和clientWidth,scrollHeight/clientHeight之间的关系不十分明确,例如:
var clientHeight=document.documentElement.clientHeight; var scrollHeight=document.documentElement.scrollHeight; console.log(clientHeight); //打印333px console.log(scrollHeight); //打印142px测试时候这个页面没有滚动条,我们发现在chrome47中,clientHeight是浏览器视口的大小,而scrollHeight是文档内容区域的大小!为了兼容不同浏览器之间的不同,我们写出下面的兼容代码:
function getDocParameter() { if(document.compatMode=='BackCompat')//怪异模式 { return{ docHeight:Math.max(document.body.scrollHeight,document.body.clientHeight), docWidth:Math.max(document.body.scrollWidth,document.body.clientHeight) } }else//标准模式 { return{ docHeight:Math.max(document.documentElement.scrollHeight,document.documentElement.clientHeight), docWidth:Math.max(document.documentElement.scrollWidth,document.documentElement.clientHeight) } } }通过重置scrollTop/scrollLeft可以让滚动条恢复到原来的位置!