2012-08-30 12 views
10

Dando dos elementos HTML abitrarios A y B en el mismo documento, ¿cómo puedo saber cuál está "más cerca" del usuario (es decir, si se superponen, cuál está oscureciendo el otro)?Comparar elementos HTML por índice z real

El W3C CSS Specification describe los contextos de apilamiento, que los motores de representación deben implementar. Sin embargo, no pude encontrar una manera de acceder a esta información en un programa de JavaScript, navegadores cruzados o no. Todo lo que puedo leer es la propiedad css z-index, que no dice mucho, ya que la mayoría de las veces está configurada en auto o, incluso cuando se expresa como un valor numérico, no es un indicador confiable de cómo se muestra realmente (si pertenecen a diferentes contextos de statcking, la comparación de los índices z es irrelevante).

Tenga en cuenta que estoy interesado en arbitrarias elementos: si ambos elementos están por debajo del puntero del ratón, sólo uno será considerado "se cernía", para que pueda encontrar fácilmente el más cercano en este caso. Sin embargo, estoy buscando una solución más general, preferiblemente una que no implique volver a implementar el algoritmo de apilamiento que el motor de renderización ya está realizando.

Actualización: quiero aclarar un poco la razón detrás de esta pregunta: Hace poco abordados a question que expone una limitación en arrastrar y soltar mecanismo de jQuery - no se necesita índices de z en cuenta al caer, por lo que si una elemento está oscureciendo a otro, aún puede realizar la operación de caída en el elemento que está "detrás". Mientras que la pregunta vinculada fue respondida para el caso particular de OP, el problema general persiste, y no hay una solución fácil que yo sepa.

alex's answer a continuación es útil, pero no es suficiente para el caso que nos ocupa: cuando se arrastra, el elemento arrastrado en sí mismo (o más precisamente, su ayudante) es el elemento de la cima bajo el cursor del ratón, por lo elementFromPoint volverá que en lugar de el próximo elemento superior, que realmente necesitamos (solución alternativa:style the cursor por lo que se coloca fuera del ayudante). Las otras estrategias de intersección que emplea jQuery también tienen en cuenta más de un punto, lo que complica la tarea de determinar el elemento superior que intersecta al ayudante de alguna manera. Ser capaz de comparar (u ordenar) los elementos por índice z real haría viable un modo de intersección de "índice z" para el caso general.

Respuesta

1

Después de algunos días de investigación creo que he reimplementado exitosamente el mecanismo de apilamiento de acuerdo con las reglas de 2016. Básicamente, he actualizado el enfoque de 2013 (publicado por el OP). El resultado es una función que compara dos nodos DOM y devuelve el que está visualmente en la parte superior.

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

Razonamiento

Hay otras maneras de determinar qué elemento está en la parte superior de la otra. Por ejemplo, document.elementFromPoint() o document.elementsFromPoint() para recordar. Sin embargo, hay muchos factores (indocumentados) que influyen en la confiabilidad de estos métodos. Por ejemplo, la opacidad, la visibilidad, los eventos de puntero, la visibilidad de la cara posterior y algunas transformaciones pueden hacer que document.elementFromPoint() no pueda probar un elemento específico. Y luego está el problema de que document.elementFromPoint() solo puede consultar el elemento superior (no los subyacentes). Esto debería resolverse con document.elementsFromPoint(), pero actualmente solo se ha implementado en Chrome. Además de eso, archivé un error con los desarrolladores de Chrome sobre document.elementsFromPoint(). Cuando se golpea la prueba de una etiqueta de anclaje, todos los elementos subyacentes pasan desapercibidos.

Todos estos problemas combinados me hicieron decidir intentar una reimplementación del mecanismo de apilamiento. El beneficio de este enfoque es que el mecanismo de apilamiento está documentado ampliamente y que puede ser probado y comprendido.

Cómo funciona

Mi enfoque re-implementa el mecanismo de apilamiento HTML. Su objetivo es seguir correctamente todas las reglas que influyen en el orden de apilamiento de los elementos HTML. Esto incluye reglas de posicionamiento, flotantes, orden DOM pero también propiedades CSS3 como opacidad, transformación y propiedades más experimentales como filtro y máscara. Las reglas parecen estar implementadas correctamente a partir de marzo de 2016, pero deberán actualizarse en el futuro cuando cambien las especificaciones y el soporte del navegador.

He reunido todo en un GitHub repository. Esperemos que este enfoque continúe funcionando de manera confiable. Aquí hay un example JSFiddle del código en acción. En el ejemplo, todos los elementos se ordenan por 'índice z' real, que es lo que el OP estaba buscando.

Las pruebas y los comentarios sobre este enfoque serían muy bienvenidos.

+0

Voy a probar su código con más cuidado cuando tenga tiempo, pero a primera vista parece estar bien. Estoy aceptando su respuesta por el momento, ya que es la más completa que tenemos hasta ahora. – mgibsonbr

2

Puede obtener las dimensiones y los desplazamientos de los elementos, y luego usar document.elementFromPoint() para determinar cuál es el elemento representado en la parte superior.

+0

¡Esto es muy útil! Esperaba un método que no requiriera un punto determinado, pero para muchos propósitos prácticos, esto ofrece una buena solución. – mgibsonbr

3

Nota: después de más de un año sin una respuesta, esta pregunta era also posted at Stack Overflow in Portuguese y - al mismo tiempo y sin una solución concluyente - algunos usuarios y me pudieron replicate the stacking mechanism in JavaScript (reinventar la rueda, pero aún así ...)

Citando el algoritmo contexto de apilamiento en el (énfasis mío) CSS2 specification:

el elemento raíz forma el contexto de apilamiento raíz. Otros contextos de apilamiento son generados por cualquier situado en el elemento (incluidos los elementos relativamente posicionados) que tiene un valor calculado de 'z-index' que no sea 'auto'. Los contextos de apilamiento no están necesariamente relacionados con bloques de contención.En los futuros niveles de CSS, otras propiedades pueden introducir contextos de pila, por ejemplo 'opacidad'

De esa descripción, he aquí una función para devolver: a) el z-index de un elemento, si se genera un nuevo apilamiento contexto; o b) undefined si no lo hace>

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

Esto debería ser capaz de apartar los elementos que forman los diferentes contextos de apilamiento. Para el resto de los elementos (y para los elementos con un igual z-index) la Appendix E dice que deben respetar "orden árbol":

Preorder recorrido en profundidad del árbol de procesamiento, en un orden lógico (no visual) para contenido bidireccional, después de tener en cuenta las propiedades que mueven las cajas.

excepción de aquellas "propiedades que se mueven las cajas alrededor", esta función SHAUD implementa correctamente el recorrido:

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

Con estas dos funciones definidas, por fin podemos crear nuestra función de comparación de elemento:

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

Aquí hay tres ejemplos que muestran este método en la práctica: Example 1, Example 2, Example 3 (lo siento, no me molesté en traducir todo a e nglish ... es exactamente el mismo código, solo diferentes funciones y nombres de variables).

Esta solución probablemente es incompleta, y debería fallar en casos extremos (aunque no pude encontrar ninguna). Si alguien tiene alguna sugerencia para mejoras, sería muy apreciado.

Cuestiones relacionadas