JS魔法堂:ノードの位置関係を判断する

15662 ワード

一、前言
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');

メソッド1: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.IE 9~11の下のSelectionタイプオブジェクトにはcontainsNodeメソッドがない.
4. IE5.5~8でSelectionタイプはありません.
 
IE下の「object Selection」と「object MSSelection」タイプについて(詳細は『JS魔法堂:詳しくはSelectionとMSSelectionタイプ』参照)
1.IE 11は[object Selection]タイプのみ
取得方法:document.getSelection()またはwindow.getSelection() 
2.IE 9~10は[object MSSelection]と[object Selection]の2種類がある
取得[object MSSelection]:document.selection 
取得[object Selection]:document.getSelection()とwindow.getSelection() 
3. IE5.5~IE 8は[object MSSelection]タイプのみ
取得方法:document.selection 
注意:document.selectionはIE特有の属性です.
 
メソッド2: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; };

欠点:IE 5と互換性がない.5~8(IE 9+、FF、Chromeともにサポート)
1. IE5.5~8 documentなし.createRange()メソッド
[object Range]、[object TextRange]、および[object Control Range]タイプについて
まず、「object Range」はW 3 C規格に準拠しており、「object TextRange」と「objectControlRange」はIE独自であることが明らかになった.
(詳しくは『JS魔法堂:Range、TextRange、ControlRangeタイプ』参照)
1.document.createRange()[object Range]オブジェクトの作成
2.Windows経由getSelection().getRangeAt({unsigned int 32}index)[object Range]オブジェクトを取得
3.document.selection.牙列缺损selection.createRangeCollection()メソッドは[object TextRange]オブジェクトを取得し、Rangeオブジェクトの内容のようにselectNodeメソッドで直接DOMクリップにバインドすることはできません.
 
方法3: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、firefox 9+、ie 5+、opera 9.64+(推定9.0+)、safari 5.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はW 3 C基準で両ノードの位置関係を比較する大きな利器と言えるので、祖父母と孫の関係だけでなく、他の関係も判断できますよ
var ret = A.compareDocumentPosition(B);
戻り値retの意味は次のとおりです.
Bits Number Meaning 00000要素が一致する000001ノードは、異なるドキュメント(またはドキュメントの外)にあります.0000102ノードBノードAより前0001004ノードAノードBより前001008ノードBノードA 010000 16ノードAノードB 100000 32ブラウザのプライベート使用
 
方法5:再帰遍歴
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; } }

利点:すべてのブラウザで共通
欠点:ノードレベルが深い場合、効率が低下します.
 
総合案1は司徒正美(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; }

総合案2、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^_^肥えたジョン