jQuery 源码解析(二十七) 样式操作模块 坐标详解
样式操作模块可用于管理dom元素的样式、坐标和尺寸,本节讲解一下坐标这一块。
对于坐标来说,jquery提供了一个offset方法用于获取第一个匹配元素的坐标或者设置所有匹配元素的坐标,还有offsetparent获取最近的定位祖先元素,position用于获取获取第一个匹配元素相对于最近定位祖先元素的坐标,如下:
- offset(options) ;返回匹配元素集合中的一个元素的文档坐标,或者设置每个元素的文档坐标,;不能带单位,默认是px,有两种使用方法:
- offset() ;返回第一个匹配元素的文档坐标
- offset(val) ;设置每个匹配的元素的文档坐标
- offsetparent(options) ;获取最近的定位祖先元素
- position() ;获取第一个匹配元素相对于最近定位祖先元素的坐标 ;如果是body元素则返回{ top: 0, left: 0 }。
也就是说如果不传递参数则获取第一个匹配元素的文档坐标,如果传递了参数(含有left、top的对象),则设置每个匹配元素的坐标.
举个栗子:
writer by:大沙漠 qq:22969969
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>document</title> <script src="http://libs.baidu.com/jquery/1.7.1/jquery.min.js"></script> <style> *{margin:0;padding:0;} div{margin:20px;width: 200px;height: 180px;position: relative;padding-top: 20px;background: #c38;} h1{margin:10px;color: #333;} </style> </head> <body> <div> <h1>123</h1> </div> <button id="b1">获取h1元素的文档坐标</button><br/> <button id="b2">获取h1元素最近的定位祖先元素</button><br/> <button id="b3">获取h1元素离最近定位祖先元素的坐标</button><br/> <button id="b4">设置h1元素的文档坐标</button> <p></p> <script> $('#b1').click(()=>{ //获取h1元素的文档坐标 console.log( $('h1').offset() ) }) $('#b2').click(()=>{ //获取h1元素最近的定位祖先元素,也就是div元素 console.log( $('h1').offsetparent() ) }) $('#b3').click(()=>{ //获取h1元素离最近定位祖先元素的坐标,也就是相对div元素的坐标 console.log( $('h1').position() ) }) $('#b4').click(()=>{ //设置h1元素的文档坐标,相对于整个文档的 $('h1').offset({top:'10',left:'10'}) }) </script> </body> </html>
我们添加了一个div和一个h1,另外,div设置了relation属性,div内的h1相对于div设置了margin属性,另外定义了四个按钮,分别用于获取h1的文档坐标、获取h1最近的定位组件元素、获取h1元素离最近定位祖先元素的坐标和修改h1元素的坐标,效果如下:
点击按钮1获取的文档坐标是相对于整个文档的,按钮2获取的定位元素也就是div元素,按钮3获取的是相对于div的偏移坐标,按钮4设置h1的文档坐标,此时h1元素上会新增一个position:relative;属性,jquery经过计算在h1上设置对应的偏移地址,如下:
源码分析
offset的实现是通过getboundingclientrect整个原生api来实现的,如下:
if ( "getboundingclientrect" in document.documentelement ) { //原生方法getboundingclientrect()返回元素的窗口坐标,返回值含有4个整型属性:top、left、right、bottom jquery.fn.offset = function( options ) { var elem = this[0], box; //elem指向第一个匹配元素 if ( options ) { //如果传入了options参数 return this.each(function( i ) { //则遍历匹配元素集合 jquery.offset.setoffset( this, options, i ); //并在每个元素上调用jquery.offset.setoffset(elem,options,i)设置文档坐标。 }); } if ( !elem || !elem.ownerdocument ) { //如果没有匹配元素或匹配元素不在文档中, return null; //则不做任何处理,立即返回null } if ( elem === elem.ownerdocument.body ) { //如果elem是body元素 return jquery.offset.bodyoffset( elem ); //则调用jquery.offset.bodyoffset(body)返回body元素的文档坐标 } try { box = elem.getboundingclientrect(); //调用原生方法getboundingclientrect()返回元素的窗口坐标,用try-catch语句来'吞掉'ie可能抛出的异常。 } catch(e) {} var doc = elem.ownerdocument, //指向document对象 docelem = doc.documentelement; //指向html元素 // make sure we're not dealing with a disconnected dom node if ( !box || !jquery.contains( docelem, elem ) ) { return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; } var body = doc.body, win = getwindow(doc), //调用getwindow(doc)获取window对象 clienttop = docelem.clienttop || body.clienttop || 0, //clienttop是html或body元素的上边框厚度 clientleft = docelem.clientleft || body.clientleft || 0, //clientleft是html或body元素的左边框厚度 scrolltop = win.pageyoffset || jquery.support.boxmodel && docelem.scrolltop || body.scrolltop, //滚动条的垂直偏移 scrollleft = win.pagexoffset || jquery.support.boxmodel && docelem.scrollleft || body.scrollleft, //滚动条的水平偏移 top = box.top + scrolltop - clienttop, //第一个元素的文档上坐标 left = box.left + scrollleft - clientleft; //第一个元素的文档左坐标 return { top: top, left: left }; //返回第一个元素的文档坐标 }; } else { //不支持原生方法getboundingclientrect()时,现在大多数浏览器已经支持了,所以这里不讨论。 }
上面是获取文档坐标的,对于设置文档坐标是通过jquery.offset.setoffset()来实现的,也就是上面标红的地方,jquery.offset.setoffset的实现如下:
jquery.offset = { setoffset: function( elem, options, i ) { //设置单个元素的文档坐标。 var position = jquery.css( elem, "position" ); // set position first, in-case top/left are set even on static elem if ( position === "static" ) { //如果该元素的position属性等于static的 elem.style.position = "relative"; //则修正为relative,使得设置的样式left、top能够生效。 } var curelem = jquery( elem ), //当前元素的jquery对象 curoffset = curelem.offset(), //当前元素的文档坐标 curcsstop = jquery.css( elem, "top" ), //获取 当前元素的计算样式top,带有单位 curcssleft = jquery.css( elem, "left" ), //获取 当前元素的计算样式left,带有单位 calculateposition = ( position === "absolute" || position === "fixed" ) && jquery.inarray("auto", [curcsstop, curcssleft]) > -1, //如果当前属性样式position是absolute或fixed,并且样式left或top是auto,则设置calculateposition为true。 props = {}, curposition = {}, curtop, curleft; // need to be able to calculate position if eit her top or left is auto and position is either absolute or fixed if ( calculateposition ) { //如果calculateposition为true,修正curtop和curleft坐标。 curposition = curelem.position(); //通过.position()获取当前元素相对于最近定位祖先元素或body元素的坐标 curtop = curposition.top; //获取组件元素的top curleft = curposition.left; //获取组件的left } else { //将curcsstop、curcssleft解析为数值,以便参与计算。 curtop = parsefloat( curcsstop ) || 0; curleft = parsefloat( curcssleft ) || 0; } if ( jquery.isfunction( options ) ) { //如果options是函数 options = options.call( elem, i, curoffset ); //则执行该函数,取其返回值作为要设置的文档坐标。 } if ( options.top != null ) { //计算最终的内联样式top props.top = ( options.top - curoffset.top ) + curtop; } if ( options.left != null ) { //计算最终的内联样式left props.left = ( options.left - curoffset.left ) + curleft; } if ( "using" in options ) { //如果参数options中有回调函数using,则调用 options.using.call( elem, props ); } else { curelem.css( props ); //否则调用.css(name,value)方法设置最终的内联样式top、left。 } } };
从源码里可以看到,如果元素有设置了absolute则会获取祖先元素的的偏移,然后经过一些运算获取最后的值,最后通过css()修改样式来实现最后的定位。
对于offsetparent来说,它的实现如下:
jquery.fn.extend({ offsetparent: function() { //获取最近的定位祖先元素,就是css position属性被设置为relative、absolute 或 fixed 的元素,返回一个jquery对象,包含所有祖先元素。 return this.map(function() { var offsetparent = this.offsetparent || document.body; //offsetparent是最近的定位祖先元素;如果没有找到,则返回body元素。 while ( offsetparent && (!rroot.test(offsetparent.nodename) && jquery.css(offsetparent, "position") === "static") ) { //如果找到的祖先元素的样式position是static,则继续沿着树向上找,直到遇到body元素或html元素为止。 offsetparent = offsetparent.offsetparent; } return offsetparent; //返回的定位组选元素将被添加到新构造的jquery对象上。 }); } })
offsetparent是原生的dom操作的api,用于获取第一个祖定位元素,所有浏览器都支持的,这里就是把它进行了一个封装而已。
position()比较简单,通过offsetparent()获取第一个祖先节点的文档坐标,然后用组件节点的top减去当前节点的top,组件节点的left减去当前节点的left,返回一个相对位置,仅此而已,代码就不贴了。
推荐阅读
-
jQuery 源码解析(二十九) 样式操作模块 尺寸详解
-
jQuery 源码分析(十三) 数据操作模块 DOM属性 详解
-
jQuery 源码解析(二十三) DOM操作模块 替换元素 详解
-
jQuery 源码解析(二十七) 样式操作模块 坐标详解
-
jQuery 源码分析(十二) 数据操作模块 html特性 详解
-
jQuery 源码解析(二十四) DOM操作模块 包裹元素 详解
-
jQuery 源码解析(二十五) DOM操作模块 html和text方法的区别
-
jQuery 源码分析(二十一) DOM操作模块 删除元素 详解
-
jQuery 源码解析(二十六) 样式操作模块 样式详解
-
jQuery 源码分析(十五) 数据操作模块 val详解