专栏首页偏前端工程师的驿站JS魔法堂:判断节点位置关系

JS魔法堂:判断节点位置关系

一、前言                          

  在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);

 三、总结                              

  尊重原创,转载请注明

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Eclipse魔法堂:任务管理器

    一、前言                                  Eclipse的任务管理器为我们提供一个方便的入口查看工程代办事宜,并定位到对应的代...

    ^_^肥仔John
  • .Net魔法堂:AssemblyInfo.cs文件详解

    一、前言                                 .net工程的Properties文件夹下自动生成一个名为AssemblyInfo.c...

    ^_^肥仔John
  • CentOS6.5菜鸟之旅:安装VirtualBox4.3

    一、下载VirtualBox的RHEL软件库配置文件               cd /etc/yum.repos.d wget http://downloa...

    ^_^肥仔John
  • Linux系统组建SVN服务器

    SVN是一款非常优秀的版本管理工具,与CVS管理工具一样,SVN 是一种跨平台的开源的版本控制系统,它会备份并记录每个文件每一次的修改更新变动

    民工哥
  • ConcurrentHashMap为什么比HashTable性能好

    Segment类继承于ReentrantLock,主要是为了使用ReentrantLock的锁,ReentrantLock的实现比 synchronized在多...

    IT技术小咖
  • 0设计经验,如何找到一份UX设计师的工作?

    UX (User Design) 设计师作为全球最受欢迎的高薪工作之一,截止至2017年,一名高级用户体验设计师的年薪已平均高达86,071美元,这也是为什么广...

    用户1372273
  • 最简单的数据抓取教程,人人都用得上

    这么简单的工具当然对环境的要求也很简单了,只需要一台能联网的电脑,一个版本不是很低的 Chrome 浏览器,具体的版本要求是大于 31 ,当然是越新越好了。目前...

    古时的风筝
  • 函数|多条件求和——SUMPRODUCT函数

    今天跟大家分享一个在多条件求和方面特别厉害的函数——SUMPRODUCT。 ▼ 也许大家对sum函数都很熟悉,知道它强大的求和功能。单数如果遇到多条件的求和场景...

    数据小磨坊
  • 在CentOS8下搭建PXC集群

    PXC是Percona XtraDB Cluster的缩写,是 Percona 公司出品的免费MySQL集群产品。PXC的作用是通过mysql自带的Galera...

    端碗吹水
  • Centos7下ELK+Redis日志分析平台的集群环境部署记录

    之前的文档介绍了ELK架构的基础知识(推荐参考下http://blog.oldboyedu.com/elk/),日志集中分析系统的实施方案: - ELK+Red...

    洗尽了浮华

扫码关注云+社区

领取腾讯云代金券