悬停零高度元素:浏览器错误?

时间:2020-12-08 18:46:41

While working on a submenu that (un)folds as users hover a parent menu item, I ran into the following issue. Consider the following markup (below or on JSFiddle).

在用户悬停父菜单项时处理(un)折叠的子菜单时,我遇到了以下问题。考虑以下标记(在JSFiddle下面或上面)。

ul {
  max-height: 0;
  overflow: hidden;
  margin: 10px;
  padding: 0;
  border: 10px solid #bada55;
  list-style-type: none;
  transition: border-color .4s;
}
ul:hover {
  max-height: 100px;
  border-color: #de2d26;
}

ul > li {
  padding: 10px;
  pointer-events: auto;
}

.ignorant {
  pointer-events: none;
}

.offset {
  position: relative; /* This is the culprit! */
}
<ul class="ignorant">
  <li>I am ignorant of your pointer.
      If you see me, something went wrong!</li>
</ul>
<ul>
  <li>I am not. Hover me!</li>
</ul>
<ul class="ignorant offset">
  <li>I should be ignorant of your pointer.
      If you see me, something went wrong!</li>
</ul>

Setup. We have three lists here of which the contents are hidden, because the max-height of the lists is set to 0. The borders are still shown, which is expected behavior: the height directive of an element by default only applies to the content of the element.

建立。我们在这里有三个列表,其内容被隐藏,因为列表的最大高度设置为0.仍然显示边框,这是预期的行为:默认情况下元素的高度指令仅适用于内容元素。

We show the contents on hover of the list, by setting the max-height to some big enough value.

我们通过将max-height设置为足够大的值来显示列表悬停时的内容。

Now, I want to make the list visible only when the contents are hovered, not the borders. This, I believe, should be achievable using pointer-events as shown above. Ignore events on the borders and re-enable them on the items in the lists.

现在,我想只在内容悬停时才显示列表,而不是边框​​。我相信,这应该可以使用如上所示的指针事件来实现。忽略边框上的事件,并在列表中的项目上重新启用它们。

Problem. So far, so good, this works fine in Firefox and mostly in Chrome. Namely, when I position the container relative, it is possible to trigger a hover of the content in Chrome.

问题。到目前为止,这很好,这在Firefox中运行良好,主要在Chrome中运行。也就是说,当我将容器放在相对位置时,可以触发Chrome中内容的悬停。

Firefox still works as I would expect it to, not triggering a hover. When having no borders, triggering a hover is no longer possible in Chrome either.

Firefox仍然按照我的预期工作,而不是触发悬停。当没有边框时,Chrome中也不再能够触发悬停。

Question. Is this a bug in Chrome? Or is it not clearly specified how this should be handled and therefore undefined behavior? I would be interested in any of the following:
1. an explanation of what I am doing wrong, and why;
2. an explanation of why it is okay for different browsers to behave differently in this case;
3. a brief explanation of why this is a bug. In this case, I'd be happy to report one.

题。这是Chrome中的错误吗?或者是否没有明确说明应该如何处理它,因此未定义的行为?我会对以下任何一项感兴趣:1。解释我做错了什么,为什么; 2.解释为什么不同浏览器在这种情况下表现不同的原因; 3.简要解释为什么这是一个错误。在这种情况下,我很乐意报告一个。


This is tested, at the moment, with
• Firefox 52.0.1 (64-bit Linux N);
• Firefox 52.0.2 (64-bit Windows N);
• Internet Explorer 11.0.9600.17031 (64-bit Windows N);
• Chrome 57.0.2987.133 (64-bit Linux S, 64-bit Windows S);
• Chromium 57.0.2987.98 (64-bit Linux S).
S means shown, N means not shown on hover, when using relative positioning.

目前,这已经通过Firefox 52.0.1(64位Linux N)进行了测试; •Firefox 52.0.2(64位Windows N); •Internet Explorer 11.0.9600.17031(64位Windows N); •Chrome 57.0.2987.133(64位Linux S,64位Windows S); •Chromium 57.0.2987.98(64位Linux S)。 S表示,当使用相对定位时,N表示悬停时未显示。

Thanks to BoltClock for making this question better through their constructive comments.

感谢BoltClock通过他们的建设性评论使这个问题变得更好。

2 个解决方案

#1


1  

I don't have an official explanation for this, but I know it has to do with block formatting contexts (MDN, W3C). I also am quite confident @BoltClock, who has a thorough understanding of BFCs (I sometimes suspect him for writing the spec in the first place), will be able to provide an exact technical explanation, should he ever wish to do so.

我没有官方解释,但我知道它与块格式化上下文(MDN,W3C)有关。我也非常有信心@BoltClock对BFC有深入了解(我有时会怀疑他首先编写规范),如果他愿意的话,他将能够提供一个确切的技术解释。

In short, knowing the source is knowing the fix (tested and works): when you need to set position:relative on the parent, also set position:relative on the children:

简而言之,知道源是知道修复(测试和工作):当你需要设置位置:父亲的相对时,也设置位置:亲子关系:

/* ... */
.offset {
  position: relative; /* This is the culprit! */
}
.offset > * {
  position: relative; /* And this is the fix! */
}

ul {
  max-height: 0;
  overflow: hidden;
  margin: 10px;
  padding: 0;
  border: 10px solid #bada55;
  list-style-type: none;
  transition: border-color .4s;
}
ul:hover {
  max-height: 100px;
  border-color: #de2d26;
}

ul > li {
  padding: 10px;
  pointer-events: auto;
}

.ignorant {
  pointer-events: none;
}

.offset {
  position: relative; /* This is the culprit! */
}
.offset > * {
  position: relative; /* And this is the fix! */
}
<ul class="ignorant">
  <li>I am ignorant of your pointer.
      If you see me, something went wrong!</li>
</ul>
<ul>
  <li>I am not. Hover me!</li>
</ul>
<ul class="ignorant offset">
  <li>I should be ignorant of your pointer.
      If you see me, something went wrong!</li>
</ul>

That's what you wanted, right? Cross browser.

这就是你想要的,对吧?跨浏览器。


Note: it doesn't have to be relative. It has to be anything but static.

注意:它不必是相对的。它必须是静止的。

#2


2  

Hover event bubbles the same way on all browsers which is proven by this example.

Hover事件在所有浏览器上都以相同的方式冒泡,本例证明了这一点。

ul {
  max-height: 0;
  /* overflow: hidden; */
  margin: 10px;
  padding: 0;
  border: 10px solid #bada55;
  list-style-type: none;
  transition: border-color .4s;
}

ul:hover {
  max-height: 100px;
  border-color: #de2d26;
}

ul > li {
  padding: 10px;
  pointer-events: auto;
  position: relative;
  top: 100px;
  background: yellow;
}

.ignorant {
  pointer-events: none;
}

.offset {
  position: relative;
}
<ul class="ignorant offset">
  <li>Hover me! Testing :hover across browsers...</li>
</ul>

What I concluded:

我得出的结论是:

  • it doesn't matter what elements we use, what matters is their position and display property;
  • 无论我们使用什么元素,重要的是它们的位置和显示属性;
  • it doesn't happen when child has position: relative or display: inline.
  • 当孩子有位置时,它不会发生:相对或显示:内联。

Hover is fired only when mouse is over the area where child's box model covers same pixels as its parent's box model. This area can be seen on the screen just above the blue rectangle. Don't even say it's aqua or something, I'm a simple programmer, I have no idea about colors.

仅当鼠标位于子框模型覆盖与其父框模型相同的像素的区域上时才会触发悬停。可以在蓝色矩形正上方的屏幕上看到此区域。甚至不说它是aqua或者什么,我是一个简单的程序员,我不知道颜色。

悬停零高度元素:浏览器错误?

Chrome hides child element because of its parent's overflow: hidden property which is expected behavior. What's unexpected is child's event box (if I can call it that) is still "visible" and hovers over it's parent's border just like it would with overflow: visible.

Chrome隐藏了子元素,因为它的父级溢出:隐藏属性,这是预期的行为。出乎意料的是,孩子的事件框(如果我可以称之为)仍然是“可见的”并且悬停在它的父母的边界上,就像它溢出的那样:可见。

The strange thing is it doesn't happen, as I stated before, when child has position: relative or display other than block.

奇怪的是它不会发生,正如我之前所说,当孩子有位置:亲戚或显示而不是阻止。

According to W3C:

根据W3C:

Once a box has been laid out according to the normal flow or floated, it may be shifted relative to this position. This is called relative positioning. Offsetting a box (B1) in this way has no effect on the box (B2) that follows: B2 is given a position as if B1 were not offset and B2 is not re-positioned after B1's offset is applied.

一旦盒子按照正常流动布置或浮动,它可以相对于该位置移动。这称为相对定位。以这种方式偏移框(B1)对随后的框(B2)没有影响:B2被给予一个位置,好像B1没有偏移并且在施加B1的偏移之后B2没有被重新定位。

Which, to my understanding, implies that position: relative should have no effect on the boxes. The fact that it has proves that it's a Chrome bug.

根据我的理解,这意味着这个位置:亲戚应该对盒子没有影响。事实证明它是Chrome的一个bug。

As to the pointer events. MDN states:

至于指针事件。 MDN声明:

Note that preventing an element from being the target of mouse events by using pointer-events does not necessarily mean that mouse event listeners on that element cannot or will not be triggered. If one of the element's children has pointer-events explicitly set to allow that child to be the target of mouse events, then any events targeting that child will pass through the parent as the event travels along the parent chain, and trigger event listeners on the parent as appropriate. Of course any mouse activity at a point on the screen that is covered by the parent but not by the child will not be caught by either the child or the parent (it will go "through" the parent and target whatever is underneath).

请注意,通过使用指针事件来防止元素成为鼠标事件的目标,并不一定意味着不能或不会触发该元素上的鼠标事件侦听器。如果其中一个元素的子节点已明确设置指针事件以允许该子节点成为鼠标事件的目标,则当事件沿父链传播时,任何以该子节点为目标的事件都将通过父节点,并触发事件侦听器。父母酌情。当然,在父母所覆盖但不是由孩子覆盖的屏幕上的某个点上的任何鼠标活动都不会被孩子或父母捕获(它将“通过”父母和目标下面的任何东西)。

Which means Chrome is not showing the child's event box on top of its parent but actually below it. It's just that the event goes through the parent element and hits its child. This shouldn't happen with overflow: hidden though.

这意味着Chrome没有在其父级之上显示孩子的事件框,但实际上低于它。只是事件通过父元素并击中其子元素。这不应该发生溢出:隐藏但是。

#1


1  

I don't have an official explanation for this, but I know it has to do with block formatting contexts (MDN, W3C). I also am quite confident @BoltClock, who has a thorough understanding of BFCs (I sometimes suspect him for writing the spec in the first place), will be able to provide an exact technical explanation, should he ever wish to do so.

我没有官方解释,但我知道它与块格式化上下文(MDN,W3C)有关。我也非常有信心@BoltClock对BFC有深入了解(我有时会怀疑他首先编写规范),如果他愿意的话,他将能够提供一个确切的技术解释。

In short, knowing the source is knowing the fix (tested and works): when you need to set position:relative on the parent, also set position:relative on the children:

简而言之,知道源是知道修复(测试和工作):当你需要设置位置:父亲的相对时,也设置位置:亲子关系:

/* ... */
.offset {
  position: relative; /* This is the culprit! */
}
.offset > * {
  position: relative; /* And this is the fix! */
}

ul {
  max-height: 0;
  overflow: hidden;
  margin: 10px;
  padding: 0;
  border: 10px solid #bada55;
  list-style-type: none;
  transition: border-color .4s;
}
ul:hover {
  max-height: 100px;
  border-color: #de2d26;
}

ul > li {
  padding: 10px;
  pointer-events: auto;
}

.ignorant {
  pointer-events: none;
}

.offset {
  position: relative; /* This is the culprit! */
}
.offset > * {
  position: relative; /* And this is the fix! */
}
<ul class="ignorant">
  <li>I am ignorant of your pointer.
      If you see me, something went wrong!</li>
</ul>
<ul>
  <li>I am not. Hover me!</li>
</ul>
<ul class="ignorant offset">
  <li>I should be ignorant of your pointer.
      If you see me, something went wrong!</li>
</ul>

That's what you wanted, right? Cross browser.

这就是你想要的,对吧?跨浏览器。


Note: it doesn't have to be relative. It has to be anything but static.

注意:它不必是相对的。它必须是静止的。

#2


2  

Hover event bubbles the same way on all browsers which is proven by this example.

Hover事件在所有浏览器上都以相同的方式冒泡,本例证明了这一点。

ul {
  max-height: 0;
  /* overflow: hidden; */
  margin: 10px;
  padding: 0;
  border: 10px solid #bada55;
  list-style-type: none;
  transition: border-color .4s;
}

ul:hover {
  max-height: 100px;
  border-color: #de2d26;
}

ul > li {
  padding: 10px;
  pointer-events: auto;
  position: relative;
  top: 100px;
  background: yellow;
}

.ignorant {
  pointer-events: none;
}

.offset {
  position: relative;
}
<ul class="ignorant offset">
  <li>Hover me! Testing :hover across browsers...</li>
</ul>

What I concluded:

我得出的结论是:

  • it doesn't matter what elements we use, what matters is their position and display property;
  • 无论我们使用什么元素,重要的是它们的位置和显示属性;
  • it doesn't happen when child has position: relative or display: inline.
  • 当孩子有位置时,它不会发生:相对或显示:内联。

Hover is fired only when mouse is over the area where child's box model covers same pixels as its parent's box model. This area can be seen on the screen just above the blue rectangle. Don't even say it's aqua or something, I'm a simple programmer, I have no idea about colors.

仅当鼠标位于子框模型覆盖与其父框模型相同的像素的区域上时才会触发悬停。可以在蓝色矩形正上方的屏幕上看到此区域。甚至不说它是aqua或者什么,我是一个简单的程序员,我不知道颜色。

悬停零高度元素:浏览器错误?

Chrome hides child element because of its parent's overflow: hidden property which is expected behavior. What's unexpected is child's event box (if I can call it that) is still "visible" and hovers over it's parent's border just like it would with overflow: visible.

Chrome隐藏了子元素,因为它的父级溢出:隐藏属性,这是预期的行为。出乎意料的是,孩子的事件框(如果我可以称之为)仍然是“可见的”并且悬停在它的父母的边界上,就像它溢出的那样:可见。

The strange thing is it doesn't happen, as I stated before, when child has position: relative or display other than block.

奇怪的是它不会发生,正如我之前所说,当孩子有位置:亲戚或显示而不是阻止。

According to W3C:

根据W3C:

Once a box has been laid out according to the normal flow or floated, it may be shifted relative to this position. This is called relative positioning. Offsetting a box (B1) in this way has no effect on the box (B2) that follows: B2 is given a position as if B1 were not offset and B2 is not re-positioned after B1's offset is applied.

一旦盒子按照正常流动布置或浮动,它可以相对于该位置移动。这称为相对定位。以这种方式偏移框(B1)对随后的框(B2)没有影响:B2被给予一个位置,好像B1没有偏移并且在施加B1的偏移之后B2没有被重新定位。

Which, to my understanding, implies that position: relative should have no effect on the boxes. The fact that it has proves that it's a Chrome bug.

根据我的理解,这意味着这个位置:亲戚应该对盒子没有影响。事实证明它是Chrome的一个bug。

As to the pointer events. MDN states:

至于指针事件。 MDN声明:

Note that preventing an element from being the target of mouse events by using pointer-events does not necessarily mean that mouse event listeners on that element cannot or will not be triggered. If one of the element's children has pointer-events explicitly set to allow that child to be the target of mouse events, then any events targeting that child will pass through the parent as the event travels along the parent chain, and trigger event listeners on the parent as appropriate. Of course any mouse activity at a point on the screen that is covered by the parent but not by the child will not be caught by either the child or the parent (it will go "through" the parent and target whatever is underneath).

请注意,通过使用指针事件来防止元素成为鼠标事件的目标,并不一定意味着不能或不会触发该元素上的鼠标事件侦听器。如果其中一个元素的子节点已明确设置指针事件以允许该子节点成为鼠标事件的目标,则当事件沿父链传播时,任何以该子节点为目标的事件都将通过父节点,并触发事件侦听器。父母酌情。当然,在父母所覆盖但不是由孩子覆盖的屏幕上的某个点上的任何鼠标活动都不会被孩子或父母捕获(它将“通过”父母和目标下面的任何东西)。

Which means Chrome is not showing the child's event box on top of its parent but actually below it. It's just that the event goes through the parent element and hits its child. This shouldn't happen with overflow: hidden though.

这意味着Chrome没有在其父级之上显示孩子的事件框,但实际上低于它。只是事件通过父元素并击中其子元素。这不应该发生溢出:隐藏但是。