使用原生Javascript实现jQuery offset方法

什么是jQuery的offset方法?

offset() 方法返回或设置匹配元素相对于文档的偏移(位置)。

看个具体例子,直接上代码

<style type="text/css">
    *{
        margin: 0;
        padding: 0;
    }
    #box1{
        width: 400px;
        height: 400px;
        background-color:lightcoral;
        position: relative;
        left: 10px;
        top: 10px;
    }
    #box2{
        width: 200px;
        height: 200px;
        background-color:lightseagreen;
        position: absolute;
        left: 33px;
        top: 33px;
    }
</style>
<body>
    <div id="box1">
        <div id="box2">
            
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script type="text/javascript">
        console.log($("#box1").offset());//{top: 10, left: 10}
        console.log($("#box2").offset());//{top: 43, left: 43}
    </script>
</body>
实现效果截图

使用Javascript实现的思路

  • 递归
  • Element​.get​Bounding​Client​Rect()

我们通过遍历目标元素、目标元素的父节点、父节点的父节点......依次溯源,并累加这些遍历过的节点相对于其最近祖先节点(且 position 属性非 static)的偏移量,向上直到 document,累加即可得到结果。

其中,我们需要使用 JavaScript 的 offsetTop 来访问一个 DOM 节点上边框相对离其本身最近、且 position 值为非 static 的祖先元素的垂直偏移量。具体实现为:

<!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>jQuery Tiele</title>
    </head>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }

        #box1 {
            width: 400px;
            height: 400px;
            background-color: lightcoral;
            position: relative;
            left: 10px;
            top: 10px;
        }

        #box2 {
            width: 200px;
            height: 200px;
            background-color: lightseagreen;
            position: absolute;
            left: 33px;
            top: 33px;
        }
    </style>
    <body>
        <div id="box1">
            <div id="box2">

            </div>
        </div>
        <script type="text/javascript">
            const offset = ele => {
                let result = {
                    top: 0,
                    left: 0
                }
                /*
                * nodeType 属性返回以数字值返回指定节点的节点类型。
                * 如果节点是元素节点,则 nodeType 属性将返回 1。
                * 如果节点是属性节点,则 nodeType 属性将返回 2。
                * 如果节点 node.nodeType 类型不是 Element(1),则跳出;
                * 如果相关节点的 position 属性为 static,则不计入计算,进入下一个节点(其父节点)的递归。
                * 如果相关属性的 display 属性为 none,则应该直接返回 0 作为结果。
                */
                const getOffset = (node, init) => {
                    if (node.nodeType !== 1) {
                        return
                    }

                    position = window.getComputedStyle(node)['position']

                    if (typeof(init) === 'undefined' && position === 'static') {
                        getOffset(node.parentNode)
                        return
                    }

                    result.top = node.offsetTop + result.top - node.scrollTop
                    result.left = node.offsetLeft + result.left - node.scrollLeft

                    if (position === 'fixed') {
                        return
                    }

                    getOffset(node.parentNode)
                }

                // 当前 DOM 节点的 display === 'none' 时, 直接返回 {top: 0, left: 0}
                if (window.getComputedStyle(ele)['display'] === 'none') {
                    return result
                }

                let position

                getOffset(ele, true)

                return result

            }
            let box = document.getElementById('box2')
            let result = offset(box);
            console.log(result)
        </script>
    </body>
</html>

实现截图

接下来,换一种思路,用一个相对较新的API:getBoundingClientRect来实现jQuery offset方法

getBoundingClientRect 方法

返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合 。
[图片上传中...(rect.png-6dad39-1559057838375-0)]
DOMRect 对象包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。

rect.png

代码实现:

<!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>jQuery Tiele</title>
    </head>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }

        #box1 {
            width: 400px;
            height: 400px;
            background-color: lightcoral;
            position: relative;
            left: 10px;
            top: 10px;
        }

        #box2 {
            width: 200px;
            height: 200px;
            background-color: lightseagreen;
            position: absolute;
            left: 33px;
            top: 33px;
        }
    </style>
    <body>
        <div id="box1">
            <div id="box2">

            </div>
        </div>
        <script type="text/javascript">
            const offset = ele => {
                let result = {
                    top: 0,
                    left: 0
                }
                // 当前为 IE11以下, 直接返回 {top: 0,left: 0}
                if (!ele.getClientRects().length) {
                    return result
                }

                // 当前 DOM 节点的 display === 'none' 时,直接返回 {top: 0,left: 0}
                if (window.getComputedStyle(ele)['display'] === 'none') {
                    return result
                }

                result = ele.getBoundingClientRect()
                // 得到ele所在文档的HTML节点
                let document = ele.ownerDocument.documentElement

                return {
                    //docElement.clientTop 一个元素顶部边框的宽度(以像素表示)。不包括顶部外边距或内边距。clientTop 是只读的
                    top: result.top + window.pageYOffset - document.clientTop,
                    left: result.left + window.pageXOffset - document.clientLeft

                }
            }

            let box1 = document.getElementById('box1')
            let box2 = document.getElementById('box2')
            let result1 = offset(box1);
            let result2 = offset(box2);
            console.log(result1)
            console.log(result2)
        </script>
    </body>
</html>

实现截图

需要注意的细节:

  • 第一个
    node.ownerDocument.documentElement

Node.ownerDocument 只读属性会返回当前节点的顶层的 document 对象。

看个示例

// 得到p元素所在文档的HTML节点
d = p.ownerDocument; 
html = d.documentElement;
  • 第二个
    docElement.clientTop

一个元素顶部边框的宽度(以像素表示)。不包括顶部外边距或内边距。clientTop 是只读的。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容