一、前言
在polyfill querySelectorAll 和写弹出窗时都需要判断两个节点间的位置关系,通过jQuery我们可以轻松搞定,但原生JS呢?下面我将整理各种判断方法,以供日后查阅。
二、祖孙关系
html
<div id="ancestor"><div id="parent"><div id="son">son</div></div> </div> <div id="other">other</div>
common.js
var ancestor = document.getElementById('ancestor'); var parent = document.getElementById('parent'); var son = document.getElementById('son'); var other = document.getElementById('other');
方法一:通过Selection对象
/** 定义判断祖孙关系函数* @param {HTMLElement} parentNode* @param {HTMLElement} sonNode*/ var has = function(parentNode, sonNode){
if (parentNode === sonNode) return true;
var selection = window.getSelection(); selection.selectAllChildren(parentNode);var ret = selection.containsNode(sonNode, false);return ret; };// 调用 console.log(has(ancestor, son)); // 显示true console.log(has(ancestor, other)); // 显示false
缺点:仅仅FF支持,其他浏览器一律无效
1. 执行 selection.selectAllChildren(parentNode) 时,parentNode的内容会被高亮,并且原来高亮的部分将被取消;
2. chrome下, selection.containsNode()恒返回false ;
3. IE9~11下的Selection类型对象没有containsNode方法;
4. IE5.5~8下没有Selection类型;
关于IE下的[object Selection]和[object MSSelection]类型(详细可浏览《JS魔法堂:细说Selection和MSSelection类型》)
1. IE11仅有[object Selection]类型
获取方式: document.getSelection() 或 window.getSelection()
2. IE9~10有[object MSSelection]和[object Selection]两种类型
获取[object MSSelection]: document.selection
获取[object Selection]: document.getSelection() 和 window.getSelection()
3. IE5.5~IE8仅有[object MSSelection]类型
获取方式: document.selection
注意:document.selection是IE的特有属性。
方法二:通过Range对象
var has = function(parentNode, sonNode){
if (parentNode === sonNode) return true;
var r1 = document.createRange(), r2 = document.createRange();r1.selectNode(parentNode);r2.selectNode(sonNode);var startRet = r1.compareBoundaryPoints(Range.START_TO_START, r2);var endRet = r1.compareBOundaryPoints(Range.END_TO_END, r2);var ret = startRet === -1 && endRet === 1;return ret; };
缺点:不兼容IE5.5~8(IE9+、FF和Chrome均支持)
1. IE5.5~8没有 document.createRange() 方法
关于[object Range]、[object TextRange]和[object ControlRange]类型
首先明确的是[object Range]是符合W3C标准的,而[object TextRange]和[object ControlRange]是IE独有的。
(详细可浏览《JS魔法堂:细说Range、TextRange和ControlRange类型》)
1. 通过document.createRange()创建[object Range]对象
2. 通过window.getSelection().getRangeAt({unsigned int32} index)获取[object Range]对象
3. 通过document.selection.createRange()或document.selection.createRangeCollection()方法获取[object TextRange]对象,并且无法像Range对象内容通过selectNode方法直接绑定到DOM片段中。
方法三:通过contains方法
var has = function(parentNode, sonNode){return parentNode.contains(sonNode); };
console.log(has(ancestor, ancestor));// 返回true
console.log(has(ancestor, son));// 返回true
console.log(has(ancestor, other));// 返回false
优点:简单直接
缺点:兼容性问题
支持——chrome、 firefox9+、 ie5+、 opera9.64+(估计从9.0+)、safari5.1.7+
不支持——FF
方法四:通过compareDocumentPosition方法
var has = function(parentNode, sonNode){
if (parentNode === sonNode) return true;
var rawRet = parentNode.compareDocumentPosition(sonNode); var ret = !!(rawRet & 16);return ret; };
compareDocumentPosition可以算是W3C标准中比较两节点位置关系的一大利器,不仅可以判断祖孙关系,还可以判断其他关系哦
var ret = A.compareDocumentPosition(B);
返回值ret的意思如下:
Bits Number Meaning
000000 0 元素一致
000001 1 节点在不同的文档(或者一个在文档之外)
000010 2 节点 B 在节点 A 之前
000100 4 节点 A 在节点 B 之前
001000 8 节点 B 包含节点 A
010000 16 节点 A 包含节点 B
100000 32 浏览器的私有使用
方法五:递归遍历
var has = function(parentNode, sonNode){
if (parentNode === sonNode) return true;
var p = sonNode.parentNode;if (!p.ownerDocument){return false;} else if (p !== parentNode){return has(parentNode, p);}else{return true;} }
优点:所有浏览器均通用
缺点:当节点层级深时,效率较低。
综合方案一,来自司徒正美(http://m.cnblogs.com/57731/1583523.html?full=1):
//2013.1.24 by 司徒正美
function contains(parentEl, el, container) {// 第一个节点是否包含第二个节点//contains 方法支持情况:chrome+ firefox9+ ie5+, opera9.64+(估计从9.0+),safari5.1.7+if (parentEl == el) {return true;
}if (!el || !el.nodeType || el.nodeType != 1) {return false;}if (parentEl.contains ) {return parentEl.contains(el);}if ( parentEl.compareDocumentPosition ) {return !!(parentEl.compareDocumentPosition(el) & 16);}var prEl = el.parentNode;while(prEl && prEl != container) {if (prEl == parentEl)return true;prEl = prEl.parentNode;}return false;}
综合方案二,来自Sizzle(https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L688)
注意:Sizzle的contains版本是contains(ancestor,ancestor)返回false的。
// Element contains another // Purposefully does not implement inclusive descendent // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ?function( a, b ) {var adown = a.nodeType === 9 ? a.documentElement : a,bup = b && b.parentNode;return a === bup || !!( bup && bup.nodeType === 1 && (adown.contains ?adown.contains( bup ) :a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16));} :function( a, b ) {if ( b ) {while ( (b = b.parentNode) ) {if ( b === a ) {return true;}}}return false;};
综合方案三,我那又长又臭的版本^_^
var rNative = /[^{]+\{\s*\[native code\]\s*\}/; var docEl = document.documentElement; var contains = rNative.test(docEl.contains) && function(ancestor, descendant){if (ancestor === descendant) return true;ancestor = ancestor.nodeType === 9 ? ancestor.documentElement : ancestor;return ancestor.contains(descendant); } || rNative.test(docEl.compareDocumentPosition) && function(ancestor, descendant){if (ancestor === descendant) return true;ancestor = ancestor.documentElement || ancestor;return !!(ancestor.compareDocumentPosition(descendant) & 16); } || rNative.test(document.createRange) && function(ancestor, descendant){if (ancestor === descendant) return true;var r1 = document.createRange(), r2 = document.createRange();r1.selectNode(ancestor.documentElement || ancestor);r2.selectNode(descendant.documentElement || descendant);
var startRet = r1.compareBoundaryPoints(Range.START_TO_START, r2);var endRet = r1.compareBOundaryPoints(Range.END_TO_END, r2);
var ret = startRet === -1 && endRet === 1;
try{
r1.detach();
r2.detach();
}catch(e){}
return ret;
} ||
function(ancestor, descendant){
if (ancestor === descendant) return true;
var a = ancestor.documentElement || ancestor;
var b = (descendant.documentElement || descendant)['parentNode'];
while(!!b){
if (a === b) return true;
b = b.parentNode;
}
return false;
};
三、总结
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/3931818.html^_^肥子John
【推荐】加入腾讯云自媒体扶持计划,免费领取域名&服务器
· 苏宁宣布重磅福利:“7天无理由退货”线上线下统一标准
· 兴趣降温 安卓厂商无意模仿iPhone X推出3D识别
· 乐视云更名新乐视云联:仍未获云服务牌照
· 微软搜索引擎Bing改进航班 电影和比赛结果查询
· 彭博商业周刊:硅谷悄然掀起中国技术人员回国潮
» 更多新闻...
· 以操作系统的角度述说线程与进程
· 软件测试转型之路
· 门内门外看招聘
· 大道至简,职场上做人做事做管理