11

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

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

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

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

下面亚历克斯的回答很有用,但对于手头的情况还不够:拖动时,被拖动的元素本身(或更准确地说是它的助手)是鼠标光标下的最顶层元素,因此elementFromPoint将返回而不是下一个最顶层元素,即我们真的需要(解决方法: 设置光标的样式,使其放置在助手之外)。jQuery 采用的其他交集策略也不仅仅考虑了一个点,这使得确定与帮助器相交的最顶层元素的任务变得复杂。能够通过实际的 z-index 比较(或排序)元素将使“z-index 感知”相交模式在一般情况下可行。

4

3 回答 3

3

注意:在一年多没有答案之后,这个问题也被发布在 Stack Overflow 上的葡萄牙语中,并且 - 虽然仍然没有最终的解决方案 - 我和一些用户能够在 JavaScript 中复制堆栈机制(重新发明轮子,但仍然。 ..)

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

根元素形成根堆叠上下文。其他堆叠上下文由任何具有计算值 '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);
}

这应该能够将形成不同堆叠上下文的元素分开。对于其余元素(以及具有 equal 的元素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);
}

定义了这两个函数后,我们终于可以创建元素比较函数了:

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

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

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

于 2014-07-04T10:41:41.270 回答
2

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

于 2012-08-30T05:23:20.647 回答
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 存储库中。希望这种方法将继续可靠地工作。下面是一个实际代码的示例 JSFiddle。在示例中,所有元素都按实际的“z-index”排序,这是 OP 所追求的。

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

于 2016-03-01T14:32:39.113 回答