2012-08-30 9 views
10

Sind zwei HTML-Elemente A und B im selben Dokument vorhanden, wie kann ich herausfinden, welcher "näher" am Benutzer ist (dh wenn sie sich überlappen, welcher den anderen verdeckt))HTML-Elemente nach aktuellem Z-Index vergleichen

Die W3C CSS Specification beschreibt Stacking-Kontexte, die konforme Rendering-Engines implementieren sollten. Ich konnte jedoch keine Möglichkeit finden, auf diese Informationen in einem JavaScript-Programm, Cross-Browser oder nicht zuzugreifen. Alles, was ich lesen kann, ist die css z-index Eigenschaft, die per se nicht viel sagt, da die meiste Zeit auf auto eingestellt ist, oder, selbst wenn sie als numerischer Wert ausgedrückt wird, ist kein zuverlässiger Indikator dafür, wie sie tatsächlich angezeigt wird (wenn Sie gehören zu verschiedenen Statuskontexten, der Vergleich von Z-Indizes ist irrelevant.

Bitte beachten Sie, dass ich interessiert bin arbitrary elements: Wenn beide Elemente unter dem Mauszeiger sind, wird nur einer als "hovered" betrachtet, so dass ich in diesem Fall leicht den nächsten finden kann. Ich suche jedoch nach einer allgemeineren Lösung, vorzugsweise einer Lösung, bei der der Stapelalgorithmus, den die Rendering-Engine bereits ausführt, nicht erneut implementiert wird.

Update: lassen Sie mich hinter dieser Frage ein wenig den Grund klären: Ich Angriff genommen kürzlich a question, die eine Einschränkung in jQuery Drag-and-Drop-Mechanismus ausgesetzt - es ist nicht z-Indizes nicht berücksichtigt, wenn fallen, so dass, wenn ein Element verdunkelt ein anderes, es kann immer noch die Drop-Operation in dem Element ausführen, das "hinter" ist. Während die verknüpfte Frage für den OP-Einzelfall beantwortet wurde, besteht das allgemeine Problem weiterhin, und es gibt keine einfache Lösung, von der ich weiß.

alex's answer unten ist nützlich, aber nicht genug, um für den Fall auf der Hand: beim Ziehen, das gezogene Element selbst (oder genauer gesagt dessen Helfer) ist das oberste Element unter dem Mauszeiger, so wird elementFromPoint zurück es anstelle von die nächste obersten Element, das wir wirklich brauchen (Problemumgehung:style the cursor so ist es außerhalb der Helfer platziert). Die anderen Kreuzungsstrategien, die jQuery verwendet, berücksichtigen auch mehr als nur einen Punkt, wodurch die Aufgabe erschwert wird, das oberste Element zu bestimmen, das den Helfer irgendwie schneidet. Die Möglichkeit, Elemente mit dem tatsächlichen Z-Index zu vergleichen (oder zu sortieren), würde einen "z-index-aware" Schnittmodus für den allgemeinen Fall ermöglichen.

Antwort

1

Nach einigen Tagen der Forschung denke ich, dass ich den Stacking-Mechanismus nach den Regeln von 2016 erfolgreich wieder implementiert habe. Ich habe im Grunde den 2013-Ansatz aktualisiert (vom OP veröffentlicht). Das Ergebnis ist eine Funktion, die zwei DOM-Knoten vergleicht und denjenigen zurückgibt, der sich visuell oben befindet.

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

Reasoning

Es gibt andere Möglichkeiten, um festzustellen, welches Element auf der Oberseite des anderen ist. Zum Beispiel document.elementFromPoint() oder document.elementsFromPoint() Frühling in den Sinn. Es gibt jedoch viele (undokumentierte) Faktoren, die die Zuverlässigkeit dieser Methoden beeinflussen. Beispielsweise können Opazität, Sichtbarkeit, Zeigerereignisse, Rückseitensichtbarkeit und einige Transformationen dazu führen, dass document.elementFromPoint() nicht in der Lage ist, ein bestimmtes Element zu testen. Und dann gibt es das Problem, dass document.elementFromPoint() nur das oberste Element (nicht zugrunde liegende) abfragen kann. Dies sollte mit document.elementsFromPoint() gelöst werden, ist aber derzeit nur in Chrome implementiert. Darüber hinaus habe ich einen Fehler mit den Chrome-Entwicklern über document.elementsFromPoint() eingereicht. Wenn Sie einen Anker-Tag testen, bleiben alle darunterliegenden Elemente unbemerkt.

All diese Probleme zusammen haben mich dazu gebracht, eine erneute Implementierung des Stapelmechanismus zu versuchen. Der Vorteil dieses Ansatzes besteht darin, dass der Stapelmechanismus sehr ausführlich dokumentiert ist und dass er getestet und verstanden werden kann.

Wie es

Mein Ansatz neu implementiert die HTML-Stapelmechanismus funktioniert. Es zielt darauf ab, alle Regeln korrekt zu befolgen, die die Stapelreihenfolge von HTML-Elementen beeinflussen. Dazu gehören Positionierungsregeln, Floats, DOM-Reihenfolge, aber auch CSS3-Eigenschaften wie Opazität, Transformation und experimentellere Eigenschaften wie Filter und Maske. Die Regeln scheinen ab März 2016 korrekt implementiert zu sein, müssen jedoch in Zukunft aktualisiert werden, wenn sich die Spezifikationen und die Browser-Unterstützung ändern.

Ich habe alles zusammen in eine GitHub repository gelegt. Hoffentlich wird dieser Ansatz weiterhin zuverlässig funktionieren. Hier ist eine example JSFiddle des Codes in Aktion. Im Beispiel werden alle Elemente nach dem tatsächlichen 'Z-Index' sortiert, nach dem das OP gesucht hat.

Testen und Feedback zu diesem Ansatz wäre sehr willkommen!

+0

Ich werde Ihren Code sorgfältiger testen, wenn ich Zeit habe, aber auf den ersten Blick scheint es in Ordnung zu sein. Ich akzeptiere deine Antwort für jetzt, da es die vollständigste ist, die wir bisher haben. – mgibsonbr

2

Sie können die Abmessungen und Offsets der Elemente ermitteln und dann document.elementFromPoint() verwenden, um zu bestimmen, welches Element oben gerendert wird.

+0

Dies ist sehr nützlich! Ich habe auf eine Methode gehofft, die keinen bestimmten Punkt erfordert, aber für viele praktische Zwecke bietet dies tatsächlich eine gute Lösung. – mgibsonbr

3

Hinweis: nach mehr als ein Jahr ohne Antwort, diese Frage war also posted at Stack Overflow in Portuguese und - während sie noch ohne eine schlüssige Lösung - einige Nutzer und ich in der Lage waren zu replicate the stacking mechanism in JavaScript (das Rad neu erfinden, aber immer noch ...)

Zitiert die Stapelkontext Algorithmus am CSS2 specification (Hervorhebung von mir):

Das Wurzelelement bildet die Wurzel Stapelkontext. Andere Stapelkontexte werden von jedem positionierten Element (einschließlich relativ positionierter Elemente) mit einem berechneten Wert von 'z-index' anders als 'auto' generiert. Stapelkontexte müssen nicht notwendigerweise mit Blöcken verknüpft sein.Künftig Ebene der CSS, andere Eigenschaften einführen Stapel Kontexte zum Beispiel ‚Opazität‘

Aus dieser Beschreibung ist hier eine Funktion zurück: a) die z-index ein Elements, wenn es einen neuen Stapel erzeugt contex; oder b) undefined wenn es nicht>

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

tut dies sollte in der Lage sein, Elemente zu unterscheiden, die unterschiedliche Stapelkontexte bilden. Für den Rest der Elemente (und für Elemente mit einer gleichen z-index) die Appendix E sagt sollten sie „Baum Ordnung“ respektieren:

Preorder depth-first Traversal des Rendering Baumes, in logischen (nicht visuell), um für bidirektionaler Inhalt, nachdem Eigenschaften berücksichtigt wurden, die Kisten verschieben.

Außer für die „Eigenschaften, die Boxen bewegen“, shoud diese Funktion korrekt implementiert die Traversal:

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

Mit diesen beiden Funktionen definiert, können wir schließlich unser Element Vergleichsfunktion erstellen:

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; 
} 

Hier sind drei Beispiele, die diese Methode in der Praxis zeigen: , Example 2, Example 3 (tut mir leid, hat nicht alles auf e nglish ... es ist der exakt gleiche Code, nur verschiedene Funktions- und Variablennamen).

Diese Lösung ist höchstwahrscheinlich unvollständig und sollte in Randfällen fehlschlagen (obwohl ich selbst keine finden konnte). Wenn jemand Verbesserungsvorschläge hat, wird es sehr geschätzt.