【问题标题】:Compare HTML elements by actual z-index通过实际 z-index 比较 HTML 元素
【发布时间】:2012-08-24 19:05:32
【问题描述】:

给定同一文档中的两个任意 HTML 元素 A 和 B,我如何找出哪个与用户“更接近”(即如果它们重叠,则哪个会遮挡另一个)?

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

请注意,我对任意元素感兴趣:如果两个元素都在鼠标指针下方,则只有一个会被视为“悬停”,因此在这种情况下我可以轻松找到最近的一个.但是,我正在寻找一种更通用的解决方案,最好是不涉及重新实现渲染引擎已经在执行的堆叠算法的解决方案。

更新:让我澄清一下这个问题背后的原因:我最近处理了a question,它暴露了 jQuery 拖放机制的限制 - 它没有考虑 z-indexes放置时,因此如果一个元素遮挡了另一个元素,它仍然可以在“后面”的元素中执行放置操作。虽然针对 OP 特定案例回答了相关问题,但一般问题仍然存在,并且据我所知没有简单的解决方案。

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

【问题讨论】:

    标签: html css z-index webpage-rendering


    【解决方案1】:

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

    【讨论】:

    • 这很有用!我希望有一种不需要给定点的方法,但出于许多实际目的,这确实提供了一个很好的解决方案。
    【解决方案2】:

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

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

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

    根据该描述,这是一个要返回的函数: 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(对不起,没有费心把所有的东西都翻译成英文……这是完全相同的代码,只是函数和变量名不同)。

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

    【讨论】:

      【解决方案3】:

      经过几天的研究,我认为我已经成功地根据 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 所追求的。

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

      【讨论】:

      • 有时间我会更仔细地测试你的代码,但乍一看似乎还不错。我现在接受你的回答,因为它是我们迄今为止最完整的回答。
      猜你喜欢
      • 2012-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-17
      • 2013-04-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多