博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JS魔法堂:判断节点位置关系
阅读量:7302 次
发布时间:2019-06-30

本文共 6624 字,大约阅读时间需要 22 分钟。

一、前言                          

  在polyfill querySelectorAll 和写弹出窗时都需要判断两个节点间的位置关系,通过jQuery我们可以轻松搞定,但原生JS呢?下面我将整理各种判断方法,以供日后查阅。

 

二、祖孙关系                        

html

son
other

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)); // 显示trueconsole.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 itselfcontains = 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; };

 

 三、总结                              

  尊重原创,转载请注明来自:^_^肥子John

你可能感兴趣的文章