通过实际z-index比较HTML元素

时间:2012-08-30 05:20:04

标签: html css z-index webpage-rendering

在同一文档中给出两个abitrary HTML元素A和B,我怎样才能找出哪个是"更接近"对于用户(即,如果他们重叠,哪一个遮蔽了另一个)?

W3C CSS Specification描述了堆叠上下文,符合渲染引擎应该实现。但是,我无法在JavaScript程序,跨浏览器或非浏览器中找到访问此信息的方法。我所能读到的只是css z-index属性,它本身并没有多说,因为大部分时间都设置为auto,或者即使表示为数值,也不是它是如何实际显示的可靠指标(如果它们属于不同的状态上下文,则比较z索引是无关紧要的。)

请注意我对任意元素感兴趣:如果两个元素都在鼠标指针下方,则只会考虑一个元素" hovered",所以我很容易在这种情况下找到最接近的一个。但是,我正在寻找一种更通用的解决方案,最好是不需要重新实现渲染引擎已经执行的堆叠算法。

更新:让我澄清一下这个问题背后的原因:我最近解决了a question暴露了jQuery拖放机制的限制 - 它没有&#39 ; t在删除时考虑z索引,因此如果一个元素模糊了另一个元素,它仍然可以在"后面的元素中执行drop操作。虽然针对OP特定案例回答了相关问题,但一般问题仍然存在,而且我所知道的解决方案并不容易。

下面的

alex's answer是有用的,但对于手头的情况还不够:拖动时,拖动的元素本身(或者更确切地说是它的帮助器)是鼠标光标下的最顶层元素,因此elementFromPoint将返回,而不是我们真正需要的下一个最顶层元素(解决方法: style the cursor,因此它已被放置在帮助者之外)。 jQuery采用的其他交集策略也不仅仅考虑了一个点,这使得确定与助手交叉的最顶层元素的任务变得复杂化。能够通过实际z-index比较(或排序)元素将使" z-index意识到"交叉模式适用于一般情况。

3 个答案:

答案 0 :(得分:3)

  

注意:超过一年后没有回答,这个问题是also posted at Stack Overflow in Portuguese而且 - 虽然仍然没有确定的解决方案 - 一些用户和我能够replicate the stacking mechanism in JavaScript(重新发明轮子,但仍然......)

CSS2 specification(强调我的)引用堆叠上下文算法:

  

根元素形成根堆叠上下文。其他堆叠上下文由任何定位的元素(包括相对定位的元素)生成,其计算值为'z-index'而非'auto'。堆叠上下文不一定与包含块有关。在未来的CSS级别中,其他属性可能会引入堆叠上下文,例如“不透明度”

根据该描述,这是一个返回的函数:a)元素的z-index,如果它生成一个新的堆叠上下文;或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表示他们应该尊重“树顺序”:

  

在考虑移动框的属性后,以双向内容的逻辑(非可视)顺序预先对渲染树进行深度优先遍历。

除了那些“移动框周围的属性”之外,这个函数应该正确地实现遍历:

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

以下是实践中显示此方法的三个示例:Example 1Example 2Example 3(抱歉,没有费心将所有内容翻译成英文...这是完全相同的代码,只是不同的功能和变量名称。)

这个解决方案很可能是不完整的,并且应该在边缘情况下失败(尽管我自己找不到)。如果有人有任何改进建议,我们将非常感激。

答案 1 :(得分:2)

您可以获取元素的尺寸和偏移量,然后使用document.elementFromPoint()来确定哪一个是顶部渲染的元素。

答案 2 :(得分:1)

经过几天的研究,我认为我已经根据2016年的规则成功地重新实施了堆叠机制。我基本上更新了2013年的方法(由OP发布)。结果是一个比较两个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 repository中。希望这种方法能够继续可靠地运作。这是代码的example JSFiddle。在示例中,所有元素都按实际的'z-index'排序,这是OP所追求的。

非常欢迎对此方法的测试和反馈!