zoukankan      html  css  js  c++  java
  • 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,返回一个相对位置,仅此而已,代码就不贴了。

  • 相关阅读:
    使用BigQuery分析GitHub上的C#代码
    ASP.NET Core 处理 404 Not Found
    C# 7 局部函数剖析
    调试 ASP.NET Core 2.0 源代码
    Entity Framework Core Like 查询揭秘
    ASP.NET Core Razor 视图组件
    Thread 1 cannot allocate new log引起的宕机事故(转载)
    docker默认网段和主机网段冲突解决
    docker安装异常以及网络问题总结
    max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
  • 原文地址:https://www.cnblogs.com/greatdesert/p/11907246.html
Copyright © 2011-2022 走看看