近期也是有点萧索,一些乱七八糟的小事,困意十足,但是技术不能停止,不得不继续向前更新。脚步不能停呀。
哎,生活。好了不瞎BB了,进入主题。
大佬原文的意思就是,我们不背诵API,只实现API。
这个话题演变自今日头条某部门面试题。当时面试官提问:“如何获取文档中任意一个元素距离文档 document 顶部的距离?”
这个方法,它返回或者设置匹配元素相对于文档的偏移(位置)。这个方法返回的对象包含俩个整型属性:top 和 left,以像素计。如果使用JQuery,
我们可以直接调取该API获得结果。 但是通过原生 JavaScript 实现。
再次之前需要科普下API,DOM1 级定义了一个Node接口,该接口将由DOM中所有节点类型实现。这个 Node 接口在 JavaScript 中是作为Node类型实现的;除了了IE之外,在其他所有浏览器中都可以访问到这个类型。JavaScript 中的所有节点类型都继承自Node类型,因此所有节点都共享着基本属性和方法。
每一个节点都有一个nodeType属性,用于表明节点的类型。节点类型由在Node类型中定义的下列常量来表示,任何节点类型必居其一:
分别是:
Node.Element_Node(1); 元素节点
Node.Attribute_Node(2); 属性节点
Node.Text_Node(3); 文本节点
Node.CDATA_SECTION_NODE(4);
Node.ENTITY_REFERENCE_NODE(5); ( 翻译说叫实体参考节点)
Node.ENTITY_NODE(6); (翻译说是叫做实体节点)
Node.PROCESSING_INSTRUCTION_NODE(7); (翻译说是处理指令节点)
Node.COMMENT_NODE(8); (注释节点)
Node.DOCUMENT_NODE(9); (这个貌似不难理解就是DOM 节点)
Node.DOCUMENT_TYPE_NODE(10); (这个是文件类型节点)
Node.DOCUMENT_FRAGMENT_NODE(11); (这个是碎片节点)
Node.Notation_Node(12); (翻译出来是叫做符号节点)
写代码首先要排除浏览器不支持的情况,
if(someNode.nodeType == Node.ELEMENT_NODE){ // 在IE中无效
alert('Node is an element');
}
常量比较如果俩者相等,意味着someNode 确实是一个元素。由于IE没有公开Node类型的构造函数,因此上面的代码在IE中会导致错误。为了浏览器兼容。
开发人员常用的俩个节点,元素节点和文本节点。
要了解节点的具体信息需要知道俩条属性,nodeName 和 nodeValue 属性。要取的这个俩个值,需要判断当前的节点为元素节点
如果是元素节点才能有上述的属性。
关于节点关系
相当于文档树比喻成家谱。在HTML中,可以将
元素看成是元素的子元素;相对应地,也就可以将元素看成是元素的父元素。而元素,则可以看成是元素的同胞元素,因为它们都是同一个父元素的直接子元素。然后,每一个节点都有一个childNodes 属性,其中保存着一个NodeList对象。NodeList是一种数组对象,用于保存一组有序的节点,可以通过位置来访问这些及节点。请注意的是,虽然可以通过方括号语法来访问NodeList的值,而且这个对象也有lenhth属性,但它并不是Array的实例。
function convertToArray(nodes){
var array = null;
try{
array = Array.prototype.slice.call(nodes, 0); // 针对非IE 浏览器
} catch (ex) {
array = new Array();
for(var i = 0,len = nodes.length; i < len; i++){
array.push(nodes[i])
}
}
return array;
}
以下是节点属性的关系图鉴
(摘选自高级程序设计)

接下来我们看实现代码
const offset = ele => {
let result = {
top: 0,
left: 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
}
上述代码不难理解,使用递归实现。如果节点 node.nodeType 类型不是Element(1), 则跳出;如果相关节点的 position 属性为 static,则不计入计算,进入下一个节点(其父节点)的递归。如果相关属性的 display 属性 为 none,则应该直接返回 0 作为结果。
关于 position 属性不为static 的问题
我专门对大佬进行了提问。
原因是:在计算时,使用了 offsetTop,offsetTop 是一个只读属性,它返回当前元素相对于其 offsetParent 元素的顶部的距离。(敲黑板:!!!相对于其 offsetParent 元素的顶部!!!)
注意上面说的是“相对于其 offsetParent 元素的顶部的距离”。
offsetParent 是什么意思呢?它是指一个指向最近的(closest,指包含层级上的最近)包含该元素的定位元素。如果当前 node 定位 position:static 那么这个 node 就不能叫一个定位元素。——所以我们计算的时候就要将这个 node 排除。
理解的关键点:
1)offsetTop Api 的精确含义。可以参考:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetTop
2)offsetParent 的含义,可以参考:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetParent 」
- - - - - - - - - - - - - - -
下面这俩个图也是我在高程上面找到的


讲的是 offsetTop 属性的一些小的基础知识
我们换一个API ,getBoundingClientRect 方法
getBoundingClientRect 方法用来描述一个元素的具体位置,该位置下面的四个属性都是相对于视口左上角的位置而言的。对某一节点执行该方法,它的返回值是一个 DOMRect 类型的对象。这个对象表示一个矩形盒子,它含有:left、top、right 和 bottom 等只读属性。
请参考实现代码:
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()
var docElement = ele.ownerDocument.documentElement
return {
top: result.top + window.pageYOffset - docElement.clientTop,
left: result.left + window.pageXOffset - docElement.clientLeft
}
}
该实现需要注意的细节
node.ownerDocument.documentElement 的用法可能大家比较陌生,ownerDocument 是DOM节点的一个属性,它返回当前节点顶层的 document对象。ownerDocument是文档,documentElement 是根节点。事实上,ownerDocument 下含 2个节点:
<!DocType>
documentElement
docElement.clientTop, clientTop 是一个元素顶部边框的宽度,不包括顶部外边距或内边距。
除此之外,这个位置挖个坑 clientTop 有另外的几种JS API 可以查看下(暂时挖坑)
哎,路漫漫兮啊
文章采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接。