11

同じドキュメント内に2つの任意のHTML要素AとBがある場合、どちらがユーザーに「近い」か(つまり、重複している場合、どちらが他方を覆い隠しているか)を確認するにはどうすればよいですか?

W3C CSS仕様では、準拠するレンダリングエンジンが実装する必要のあるスタッキングコンテキストについて説明しています。ただし、クロスブラウザかどうかに関係なく、JavaScriptプログラムでこの情報にアクセスする方法を見つけることができませんでした。私が読むことができるのはcssプロパティだけです。ほとんどの場合、またはは数値として表現されている場合でも、実際にどのように表示されるかを示す信頼できる指標ではないため、z-indexそれ自体はあまり意味がありません(それらが属している場合)auto異なるstatckingコンテキストでは、z-indexesの比較は関係ありません)。

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

更新:この質問の背後にある理由を少し明確にしましょう:私は最近、jQueryのドラッグアンドドロップメカニズムの制限を明らかにした質問に取り組みました-ドロップ時にz-indexが考慮されないため、要素が別の要素を覆い隠している場合、それでも、「背後」にある要素でドロップ操作を実行できます。リンクされた質問はOPの特定のケースで回答されましたが、一般的な問題は解決せず、私が知っている簡単な解決策はありません。

以下のalexの答えは便利ですが、当面の場合には十分ではありません。ドラッグすると、ドラッグされた要素自体(より正確にはそのヘルパー)がマウスカーソルの下の一番上の要素になるため、次の一番上の要素の代わりにそれelementFromPointを返します。本当に必要です(回避策:ヘルパーの外側にカーソルが配置されるようにカーソルのスタイルを設定します)。jQueryが採用している他の交差戦略も、1つ以上のポイントを考慮に入れているため、ヘルパーと交差する最上位の要素を決定するタスクが複雑になります。実際のz-indexで要素を比較(またはソート)できると、一般的なケースで「z-index対応」交差モードが実行可能になります。

4

3 に答える 3

3

注: 1年以上回答がなかった後、この質問はポルトガル語のStack Overflowにも投稿され、決定的な解決策はありませんでしたが、一部のユーザーと私はJavaScriptでスタックメカニズムを複製できました(車輪の再発明ですが、それでも)。 ..)

CSS2仕様でのスタッキングコンテキストアルゴリズムの引用(私の強調):

ルート要素は、ルートスタッキングコンテキストを形成します。他のスタッキングコンテキストは、「auto」以外の「z-index」の計算値を持つ任意の配置された要素(比較的配置された要素を含む)によって生成されます。スタッキングコンテキストは、必ずしもブロックの包含に関連しているわけではありません。CSSの将来のレベルでは、他のプロパティがスタッキングコンテキストを導入する可能性があります(例:「不透明度」)

その説明から、以下を返す関数がありz-indexます。a)新しいスタッキングコンテックスを生成する場合の要素の。または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)については、付録Eは「ツリーの順序」を尊重する必要があると述べています。

ボックスを移動するプロパティを考慮した後、双方向コンテンツの論理的(視覚的ではない)順序で、深さ優先探索を事前に並べ替えます。

これらの「ボックスを移動するプロパティ」を除いて、この関数はトラバーサルを正しく実装する必要があります。

/* 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);
}

これらの2つの関数を定義すると、最終的に要素比較関数を作成できます。

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つの例を次に示します。例1例2例3(申し訳ありませんが、すべてを英語に翻訳する必要はありませんでした。まったく同じコードで、関数と変数の名前が異なります)。

この解決策はおそらく不完全であり、エッジケースでは失敗するはずです(私は自分自身を見つけることができませんでしたが)。誰かが改善のための提案があれば、それは本当にありがたいです。

于 2014-07-04T10:41:41.270 に答える
2

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

于 2012-08-30T05:23:20.647 に答える
1

数日間の調査の結果、2016年のルールに従ってスタッキングメカニズムを正常に再実装できたと思います。基本的に2013年のアプローチを更新しました(OPによる投稿)。結果は、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()頭に浮かぶ。ただし、これらのメソッドの信頼性に影響を与える多くの(文書化されていない)要因があります。たとえば、不透明度、可視性、ポインタイベント、背面可視性、および一部の変換によりdocument.elementFromPoint()、特定の要素のヒットテストができなくなる場合があります。そしてdocument.elementFromPoint()、最上位の要素のみを照会できる(基になる要素は照会できない)という問題があります。これはで解決する必要がありますdocument.elementsFromPoint()が、現在はChromeでのみ実装されています。それに加えて、私はChrome開発者にについてバグを報告しましたdocument.elementsFromPoint()。アンカータグのヒットテストを行うと、基礎となるすべての要素が見過ごされます。

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

使い方

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

すべてをGitHubリポジトリにまとめました。うまくいけば、このアプローチは確実に機能し続けるでしょう。これは、動作中のコードのJSFiddleの例です。この例では、すべての要素が実際の「z-index」でソートされています。これは、OPが求めていたものです。

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

于 2016-03-01T14:32:39.113 に答える