2012-08-30 12 views
10

同じ文書内の2つの任意のHTML要素AとBが与えられている場合、どちらがユーザーに「近く」いるかをどのように見つけることができますか(つまり、 )?実際のZ-インデックスでHTML要素を比較する

W3C CSS Specificationには、対応するレンダリングエンジンが実装すべきスタッキングコンテキストが記述されています。しかし、私はこの情報にJavaScriptプログラム、クロスブラウザーでアクセスする方法を見つけることができませんでした。私が読むことができるのは、ほとんどの時間がautoに設定されているか、または数値として表現されていても実際にどのように表示されているかを示す信頼できる指標ではないため、多くのことを言っているわけではありません(css z-indexプロパティです。それらは異なる統計コンテキストに属し、zインデックスを比較することは無関係です)。

任意の要素に興味があります:両方の要素がマウスポインタの下にある場合、1つだけが「ホバリング」とみなされるため、この場合最も近いものを簡単に見つけることができます。しかし、私は、より一般的な解決策を探しています。できれば、レンダリングエンジンが既に実行しているスタッキングアルゴリズムを再実装する必要がありません。

アップデート:私はビットにこの質問の背後にある理由を明確にしましょう:私は最近、jQueryのドラッグ&ドロップのメカニズムに制限を暴露a question取り組み - 、落下時には、アカウントにZ-インデックスを取ることはありませんがもしそうであれば要素が別の要素を隠している場合でも、要素の「後ろ」にドロップ操作を実行できます。リンクされた質問は、OPの特定のケースのために答えられたが、一般的な問題は持続し、私が知っている簡単な解決策はありません。以下

alex's answer手元場合のために十分に有用である、ではない:ドラッグすると、ドラッグされた要素自体(又はより正確にそのヘルパー)は、マウスカーソルの下の最上位の要素であるので、elementFromPointが返され、それは代わりに次の最上位の要素、これは本当に必要です(回避策:style the cursorだから、ヘルパーの外に置かれます)。 jQueryが採用している他の交差戦略でも、1つのポイント以上のものが考慮されるため、ヘルパーと交差する一番上の要素を決定する作業が複雑になります。実際のZ-インデックスで要素を比較(またはソート)できると、一般的なケースでは「z-インデックス対応」の交差モードが実行可能になります。

答えて

1

数日間の研究の後、私は2016年のルールに従って積み重ねメカニズムをうまく再実装したと思います。私は基本的に2013年アプローチを更新しました。結果は、2つのDOMノードを比較し、視覚的に上にあるものを返します。

front = $.fn.visuallyInFront(document.documentElement, document.body); 
// front == <body>...</body> because the BODY node is 'on top' of the HTML node 

推論

他の上にある要素を決定する他の方法があります。たとえば、document.elementFromPoint()またはdocument.elementsFromPoint()のようになります。しかし、これらの方法の信頼性に影響を与える多くの(文書化されていない)要因が存在する。例えば、opacity、visibility、pointer-events、backface-visibility、およびいくつかの変換は、document.elementFromPoint()が特定の要素をヒットできないことを引き起こす可能性があります。そして、document.elementFromPoint()は、最上位の要素(基底要素ではない)のみを照会できるという問題があります。これはdocument.elementsFromPoint()で解決する必要がありますが、現在はChromeでのみ実装されています。それに加えて、私はdocument.elementsFromPoint()についてChrome開発者にバグを提出しました。アンカータグのヒット時に、すべての基本要素が気付かれなくなります。

これらの問題を合わせると、私はスタッキングメカニズムの再実装を試みることになりました。このアプローチの利点は、スタッキングメカニズムが非常に広範に文書化されており、それをテストし理解できることです。

どのように動作する

私のアプローチの再実装HTMLスタッキングメカニズムを。 HTML要素の積み重ね順序に影響を与えるすべての規則に正しく従うことを目指しています。これには、位置付けルール、浮動小数点数、DOM順序だけでなく、不透明度、変形、フィルタやマスクなどのより実験的なプロパティなどのCSS3プロパティも含まれます。このルールは2016年3月現在で正しく実装されているようですが、将来、仕様やブラウザのサポートが変更されたときに更新する必要があります。

私はGitHub repositoryにすべてをまとめました。うまくいけば、このアプローチは確実に継続して働くはずです。ここにはコードのexample JSFiddleがあります。この例では、すべての要素が実際の 'z-index'によってソートされています。

このアプローチのテストとフィードバックは大歓迎です!

+0

私は時間があるときにあなたのコードをより慎重にテストしますが、一見一見うまく見えます。私はあなたが今までに持っている最も完全なものなので、あなたの答えを受け入れています。 – mgibsonbr

2

要素の寸法とオフセットを取得し、document.elementFromPoint()を使用して、どの要素が上にレンダリングされる要素かを判断できます。

+0

これは非常に便利です!私は与えられた点を必要としない方法を望んでいましたが、実際には多くの実用的な目的のためにこれは良い解決策を提供しています。 – mgibsonbr

3

注:答えずに1年以上後に、この質問はalso posted at Stack Overflow in Portugueseだった - まだ決定的な解決策なしながら - 一部のユーザーと私はreplicate the stacking mechanism in JavaScriptすることができた(...車輪の再発明が、それでも)

CSS2 specification(強調鉱山)にてスタッキングコンテクストアルゴリズムを引用:

ルート要素は、ルートスタックコンテキストを形成します。他の積み重ね文脈は、要素(相対配置要素を含む)によって生成され、 'auto'以外の 'z-index'の計算値を有するによって生成される。スタッキングコンテキストは必ずしもブロックを含むものとは関係ありません。CSSの将来のレベルでは、他のプロパティは、その記述から、例えばコンテキスト、を積み重ねる「不透明度」

を導入することが、ここで返す関数です:要素のa)のz-index、それは新しいスタックを作成する場合contex;またはb)undefinedそれがない場合はありません>

function zIndex(ctx) { 
    if (!ctx || ctx === document.body) return; 

    var positioned = css(ctx, 'position') !== 'static'; 
    var hasComputedZIndex = css(ctx, 'z-index') !== 'auto'; 
    var notOpaque = +css(ctx, 'opacity') < 1; 

    if(positioned && hasComputedZIndex) // Ignoring CSS3 for now 
     return +css(ctx, 'z-index'); 
} 

function css(el, prop) { 
    return window.getComputedStyle(el).getPropertyValue(prop); 
} 

これは、異なるスタッキングコンテキストを形成する要素を離れて設定することができるはずです。残りの要素のために(と等しいz-indexを持つ要素のための)Appendix Eは、彼らが「木の順序」を尊重すべきであると言うための:(視覚的ではない)論理的な順序でレンダリングツリーの

予約限定の深さ優先探索、ボックスを移動するプロパティを考慮した上で、双方向のコンテンツを作成できます。それらの「周りにボックスを移動する性質」を除き

トラバーサル実装し、この関数は正しくshoud:

:定義されたこれらの二つの機能を持つ

/* a and b are the two elements we want to compare. 
* ctxA and ctxB are the first noncommon ancestor they have (if any) 
*/ 
function relativePosition(ctxA, ctxB, a, b) { 
    // If one is descendant from the other, the parent is behind (preorder) 
    if ($.inArray(b, $(a).parents()) >= 0) 
     return a; 
    if ($.inArray(a, $(b).parents()) >= 0) 
     return b; 
    // If two contexts are siblings, the one declared first - and all its 
    // descendants (depth first) - is behind 
    return ($(ctxA).index() - $(ctxB).index() > 0 ? a : b); 
} 

を、我々は最終的に私たちの要素の比較関数を作成することができます

function inFront(a, b) { 
    // Skip all common ancestors, since no matter its stacking context, 
    // it affects a and b likewise 
    var pa = $(a).parents(), ia = pa.length; 
    var pb = $(b).parents(), ib = pb.length; 
    while (ia >= 0 && ib >= 0 && pa[--ia] == pb[--ib]) { } 

    // Here we have the first noncommon ancestor of a and b 
    var ctxA = (ia >= 0 ? pa[ia] : a), za = zIndex(ctxA); 
    var ctxB = (ib >= 0 ? pb[ib] : b), zb = zIndex(ctxB); 

    // Finds the relative position between them  
    // (this value will only be used if neither has an explicit 
    // and different z-index) 
    var relative = relativePosition(ctxA, ctxB, a, b); 

    // Finds the first ancestor with defined z-index, if any 
    // The "shallowest" one is what matters, since it defined the most general 
    // stacking context (affects all the descendants) 
    while (ctxA && za === undefined) { 
     ctxA = ia < 0 ? null : --ia < 0 ? a : pa[ia]; 
     za = zIndex(ctxA); 
    } 
    while (ctxB && zb === undefined) { 
     ctxB = ib < 0 ? null : --ib < 0 ? b : pb[ib]; 
     zb = zIndex(ctxB); 
    } 

    // Compare the z-indices, if applicable; otherwise use the relative method 
    if (za !== undefined) { 
     if (zb !== undefined) 
      return za > zb ? a : za < zb ? b : relative; 
     return za > 0 ? a : za < 0 ? b : relative; 
    } 
    else if (zb !== undefined) 
     return zb < 0 ? a : zb > 0 ? b : relative; 
    else 
     return relative; 
} 

実際にこの方法を示す3つの例があります:Example 1Example 2Example 3(申し訳ありませんが、 nglish ...これはまったく同じコードですが、関数と変数の名前はまったく異なります)。

このソリューションは不完全な可能性が高く、エッジケースでは失敗するはずです(自分では見つけられませんでしたが)。改善の提案があれば、本当に感謝しています。

関連する問題