assailant assailant - 2 months ago 5
CSS Question

hover, z-index and layering... a convoluted mess?

I'm a web developer noob and hoping someone with more experience can help.

<div id="domain-background-container">
<div class="domain-hotspots">
<div id="hotspot">
<p class="bubble">I'm red on hover</p>
</div>
</div>
</div>

<div id="page-nav" >
<p>I'm red on hover</p>
</div>


html, body { /* <--- (1) */
height: 100%; }

#domain-background-container {
position: fixed;
z-index: -10; } /* <--- (2) */

.domain-hotspots {
width: 100vw;
height: 100vh;
position: fixed;
background: blue; }

#hotspot {
width: 2em;
height: 2em;
background: green; }

#hotspot:hover .bubble {
background: red; }

#page-nav {
width: 100%;
text-align: center;
background: yellow; }

#page-nav:hover p {
color: red; }


Here's the jsfiddle.

The problem is that #hotspot (that is the green square) doesn't enter the hover state when the mouse cursor is placed over it.

I'm looking to have four things explained:


  1. Can someone provide or point me to a detailed explanation of the mechanics preventing #hotspot from entering the hover state?

  2. Removing CSS ruleset (1) [height: 100% for html and body] allows #hotspot to enter the hover state. This isn't a viable solution in my project, but for curiosity's stake, why does this make a difference.

  3. Removing the the z-index of CSS ruleset (2) also allows #hotspot to enter the hover state. Unfortunately, this also screws up z-ordering as .domain-hotspots (blue) then covers #page-nav (yellow) which isn't acceptable. Theoretically I could position and raise the z-value of #page-nav. While that would work for this simple jsfiddle I'm loath to do that for a bigger project where I'd have to apply that to every element following .domain-hotspots (which shouldn't even be necessary, because to my understanding elements with unmodified z-index which follow .domain-hotspots in the html file should be rendered on top of .domain-hotspots in the viewport) :-/

  4. I can't use the two "solutions" I just mentioned, so what solution remains?



I'd really appreciate any knowledge people can share on this! I'm stumped. I'm not even sure what I should google for to research the issue, as I don't know what's causing this behaviour.

Answer

Can someone provide or point me to a detailed explanation of the mechanics preventing #hotspot from entering the hover state?

<body> is covering it up.
This can be seen by using the "Inspect element" function of a browser's dev tools.
(Due to the way z-index works though, giving it a background does not put that background in front of the div.)

Removing CSS ruleset (1) [height: 100% for html and body] allows #hotspot to enter the hover state. This isn't a viable solution in my project, but for curiosity's stake, why does this make a difference.

With height: 100%:

screenshot

Without height: 100%:

screenshot

[...] to my understanding elements with unmodified z-index which follow .domain-hotspots in the html file should be rendered on top of .domain-hotspots in the viewport.

From the CSS Level 2 draft, ยง 9.9.1:

Within each stacking context, the following layers are painted in back-to-front order:

  1. the background and borders of the element forming the stacking context.
  2. the child stacking contexts with negative stack levels (most negative first).
  3. the in-flow, non-inline-level, non-positioned descendants.
  4. the non-positioned floats.
  5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
  6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
  7. the child stacking contexts with positive stack levels (least positive first).
  • #page-nav is category 3 due to the default display: block on any <div>.
  • #domain-background-container is category 2 due to position: fixed, which removes it from the flow and creates a child stacking context, and due to its negative z-index.

Category 3 gets rendered over category 2, so #page-nav is in front of #domain-background-container.
Now when you remove the z-index from #domain-background-container, or make it 0 or positive, it becomes category 6 or 7, thus being rendered after (i.e. over) #page-nav.

Theoretically I could position and raise the z-value of #page-nav. While that would work for this simple jsfiddle I'm loath to do that for a bigger project where I'd have to apply that to every element following .domain-hotspots. [...] I can't use the two "solutions" I just mentioned, so what solution remains?

You don't actually have to raise its z-value, you merely have to position it. position: relative will do, even.
And applying that to every element following #domain-background-container is no miracle either:

#domain-background-container ~ * {
    position: relative;
}

Works quite well:

html, body {      /* <--- (1) */
  height: 100%; }

#domain-background-container {
  position: fixed;
  /*z-index: -10;*/ }  /* <--- (2) */

#domain-background-container ~ * {
  position: relative; }

.domain-hotspots {
  width: 100vw;
  height: 100vh;
  position: fixed; 
  background: blue; }

  #hotspot {
    width: 2em;
    height: 2em;
    background: green; }

  #hotspot:hover .bubble {
    background: red; }

#page-nav {
  width: 100%;
  text-align: center;
  background: yellow; }

#page-nav:hover p {
  color: red; }
<div id="domain-background-container">
  <div class="domain-hotspots">
    <div id="hotspot">
      <p class="bubble">I'm red on hover</p>
    </div>
  </div>
</div>

<div id="page-nav" >
  <p>I'm red on hover</p>
</div>

Admittedly though, there are situations where it is not feasible to position all those elements.

Another solution though, is to simply position the body element, which then allows you to give it a z-index of its own:

body {
    position: relative;
    z-index: -11;
}

Demo:

html, body {      /* <--- (1) */
  height: 100%; }

body {
    position: relative;
    z-index: -11; }

#domain-background-container {
  position: fixed;
  z-index: -10; }  /* <--- (2) */

#domain-background-container ~ * {
  position: relative; }

.domain-hotspots {
  width: 100vw;
  height: 100vh;
  position: fixed; 
  background: blue; }

  #hotspot {
    width: 2em;
    height: 2em;
    background: green; }

  #hotspot:hover .bubble {
    background: red; }

#page-nav {
  width: 100%;
  text-align: center;
  background: yellow; }

#page-nav:hover p {
  color: red; }
<div id="domain-background-container">
  <div class="domain-hotspots">
    <div id="hotspot">
      <p class="bubble">I'm red on hover</p>
    </div>
  </div>
</div>

<div id="page-nav" >
  <p>I'm red on hover</p>
</div>