这篇文章发布于 2019年03月2日,星期六,20:09,归类于 JS API。 阅读 20856 次, 今日 18 次 6 条评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=8527
本文可全文转载,个人网站无需授权,只要保留原作者、出处以及文中链接即可,任何网站均可摘要聚合,商用请联系授权。
一、快速了解
Node.compareDocumentPosition()方法可以用来对比两个HTML节点在文档中的位置关系,包括前后,父子,自身以及跨文档。不仅是DOM节点,文本节点,注释节点甚至属性节点的位置关系都可以判定,很强。
IE9+浏览器支持,IE8可以借助sourceIndex
来判定。
二、深入理解
compareDocumentPosition
语法如下:
compareValue = node.compareDocumentPosition(otherNode)
注意:这里有个容易记不清的地方,到底返回的位置关系是node
相对于otherNode
,还是otherNode
相对于node
呢?结果居然返回的是otherNode
相对于node
的位置!
可能是中国文化和外国文化的区别。中国文化内敛,固守眷恋,关注自身,言必吾当如何如何;外国文化向外,武力侵略,指手划脚,都是你该如何如何。
由于这些API语法是老外发明的,所以,概念上就按照老外的认识来的。节点node对otherNode发起了一个文档位置判断的挑战,最终的结果不是我赢了或我输了,而是你输了或者你赢了。也就是otherNode
你在前面,otherNode
你在后面,这样子的。
返回值
compareValue
是返回值,是整数值,可能的值如下表:
二进制 | 返回值 | 释义 | 对应常量 |
---|---|---|---|
000000 | 0 | 节点一致 | – |
000001 | 1 | 节点在不同的文档(或者一个在文档之外) | Node.DOCUMENT_POSITION_DISCONNECTED |
000010 | 2 | 节点 otherNode 在节点 node 之前 | Node.DOCUMENT_POSITION_PRECEDING |
000100 | 4 | 节点 otherNode 在节点 node 之后 | Node.DOCUMENT_POSITION_FOLLOWING |
001000 | 8 | 节点 otherNode 包含节点 node | Node.DOCUMENT_POSITION_CONTAINS |
010000 | 16 | 节点 otherNode 被节点 node 包含 | Node.DOCUMENT_POSITION_CONTAINED_BY |
100000 | 32 | 特定的节点位置,依赖于DOM实现 | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
– | 组合值(如34,32+2) | 复合节点关系 | – |
关于复合节点关系的组合返回值稍后展开,我们先开看简单的位置关系熟悉下此API。
// 返回值是 0 document.body.compareDocumentPosition(document.body); // 返回值是 4,<body>在<head>后面 document.head.compareDocumentPosition(document.body)
接下来,我们看下复合节点关系下返回的组合值,如下:
// 返回值是 10,8 + 2 document.body.compareDocumentPosition(document.documentElement); // 返回值是 20,16 + 4 document.documentElement.compareDocumentPosition(document.body)
返回值分别是10
和20
。其中:
10
是8+2
的组合值,8
表示documentElement
包含body
,2
表示documentElement
在body
前面。20
是16+4
的组合值,16
表示body
被documentElement
包含,4
表示body
在documentElement
后面。
因此,我们实际开发的时候,不能直接等于==
某个常量值判断位置关系,而需要借助其他运算符,例如位运算符&
。在JS中,一个&
表示运算符按位与,就是把两个二进制数按每一位比较,同时为1
才得1
,只要一个为0
就为0
,最终的二进制值就是运算值。
例如数字2
和8
比较,如下图:
2 & 8; // 结果是00000,也就是0
如果是数字2
和数字10
比较呢?如下图:
2 & 10; // 结果是00010,也就是2
由于compareDocumentPosition
返回值都是标准的只有1位是1的二进制值,因此,要判断前后或者内外节点位置关系直接按位与,结果不是0就可以了。
例如:
if (document.body.compareDocumentPosition(document.documentElement) & Node.DOCUMENT_POSITION_PRECEDING) {
// document.documentElement在document.body前面
// ...
}
一个字符&
而不是==
哟。
三、进一步深入
compareDocumentPosition
还可以用来比对HTML属性节点的前后位置关系,例如如下HTML:
<img id="compareImg" src="./mm.jpg" alt="示意图">
var altNode = compareImg.getAttributeNode('alt');
var srcNode = compareImg.getAttributeNode('src');
// 结果是34 = 32 + 2
console.log(altNode.compareDocumentPosition(srcNode));
如果HTML代码中的'src'
和'alt'
属性位置调换下,如下:
<img id="compareImg" alt="示意图" src="./mm.jpg">
则结果是:
// 结果是36 = 32 + 4
console.log(altNode.compareDocumentPosition(srcNode));
如何出现返回值1?
当我们的节点在内存中而不再文档页面中,或者我们的节点在页面内的其它iframe中的时候,会出现返回值包含1
。
例如:
// 结果是35 = 32 + 2 + 1
document.createElement('div').compareDocumentPosition(document.body)
也就是特定实现(32),document.body
在前(2),两者文档无关联(1)。
又例如我们直接借助Blob动态创建一个非外链iframe,代码如下:
var htmlIframe = '<img id="img" src="https://.../mm.jpg" onclick="console.log(this.compareDocumentPosition(window.parent.document.body))">'; var iframe = document.createElement('iframe'); var blob = new Blob([htmlIframe], { 'type': 'text/html'}); iframe.src = URL.createObjectURL(blob); iframeBlob1.appendChild(iframe);
实时效果如下,点击妹子图片,看看输出的结果是?
结果是35(32 + 2 + 1),如下图:
四、应用场景
什么时候我们需要知道节点的前后位置关系呢?在一些animation+绝对定位实现的slide页面过场的场景下,由于回到之前页面的slide方向是相反的,此时我们就可以通过节点的前后位置关系判断页面是从右往左出去,还是从左往右出去。
例如这个demo页面,点击底部的选项卡,可以看到不同的过场方向,就是借助compareDocumentPosition方法判断的。
也可以点击下面的视频体验:
五、结语
我还是太年轻,一开始把这个API想的太简单了,以为就是一些位置返回一些固定的数字,没想到返回的位置居然是混合的,好处是我们可以准确知晓某些元素节点在页面中的复杂位置关系,不足就是增加了我们的学习和理解成本。
其实我们实际开发很少有场景需要知道非常详情的位置关系的。
实际开发推荐使用Node.DOCUMENT_POSITION_DISCONNECTED
这样的常量进行比对,更容易理解,可读性更好。关键问题不好记忆,没办法,到时候查文档,或者到本站搜索compareDocumentPosition
。
好了,就说这些,感谢阅读!
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=8527
(本篇完)
- DOM小测28期 - DOM节点文档前后位置判断 (0.499)
- 从今天开始,请叫我Node文本节点处理大师 (0.342)
- JavaScript实现新浪微博文字放大显示动画效果 (0.299)
- iframe和HTML5 blob实现JS,CSS,HTML直接当前页预览 (0.209)
- 浏览器IMG图片原生懒加载loading="lazy"实践指南 (0.168)
- 小tip:iframe高度动态自适应 (0.154)
- HTML5 postMessage iframe跨域web通信简介 (0.154)
- DOMParser和XMLSerializer两个API简介 (0.114)
- 小tips: JS DOM innerText和textContent的区别 (0.114)
- 小tip:我是如何初体验uglifyjs压缩JS的 (0.086)
- 小tips: 纯前端JS读取与解析本地文本类文件 (RANDOM - 0.055)
小姐姐的那个例子 我这里是37
macOS chrome 版本 73
我还是太年轻,一开始完全不认识这个API。嗯嗯,一定还是太年轻了
if (document.body.compareDocumentPosition(document.documentElement) & Node.DOCUMENT_POSITION_PRECEDING) {
// document.documentElement在document.body前面
// …
}
这里有不太明白,Node.DOCUMENT_POSITION_PRECEDING 这个是什么呢?
又看了几遍,发现MDN 例子里也是这样的,原来这是个常量,我已理解,不麻烦老师解答了
文中八的二进制和十的二进制应该是 01000 和 01010
感谢啊。以前没太明白掩码是啥。
现在明白了,这个画直线也是叫掩码:http://tieba.baidu.com/p/5890635950