Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I establish a new root level z-index in a child of a fixed element?

Tags:

html

css

Given the following HTML:

#header {
  position: fixed;
  z-index: 1;
  background-color: red;
  width: 100%;
  height: 150px;
  top: 0;
}

#drop {
  position: absolute;
  top: 5px;
  width: 100px;
  background-color: green;
  height: 400px;
  z-index: 3;
}

#footer {
  position: fixed;
  z-index: 2;
  background-color: blue;
  width: 100%;
  height: 150px;
  bottom: 0;
}
<div id="header">
  <div id="drop">
  </div>
</div>

<div id="footer">
</div>

How do I amend the CSS only, so #drop is considered in front of both #header and #footer. While maintaining that #footer is in front of #header?

like image 341
Sam Saffron Avatar asked Sep 22 '14 01:09

Sam Saffron


1 Answers

According to the current standard, this is as easy as removing the z-index from #header. With the default z-index of auto, the header does not establish a new stacking context, and thus the dropdown and the footer belong to the same stacking context, and #drop's z-index of 3 pushes it above the footer. You can even remove the z-index from the footer as well; because it comes after the header in document order, it will still be on top.

Try it out:

#header {
  position: fixed;
  background-color: red;
  width: 100%;
  height: 150px;
  top: 0;
}

#drop {
  position: absolute;
  top: 5px;
  width: 100px;
  background-color: green;
  height: 400px;
  z-index: 3;
}

#footer {
  position: fixed;
  background-color: blue;
  width: 100%;
  height: 150px;
  bottom: 0;
}
<div id="header">
  <div id="drop">
  </div>
</div>

<div id="footer">
</div>

Works great. In Firefox.

But since you're probably using Chrome, not so much. Chrome has decided to ignore the standard for mobile performance reasons; in Chrome, a position: fixed element always establishes a new stacking context, regardless of its z-index value. As BoltClock points out, this change may sooner or later appear in other browsers as well.

And thus in Chrome, since the dropdown is a descendant of the header, its stacking context is the one established by the header, and that stacking context is below the footer, per your requirement. So in a cross-browser future-proof way and within your constraints, this is utterly impossible.

If you want to do this without your JavaScript workaround, I see two options:

Move the dropdown out of the header

That works easily enough in your example

#header {
  position: fixed;
  z-index: 1;
  background-color: red;
  width: 100%;
  height: 150px;
  top: 0;
}

#drop {
  position: fixed;
  top: 5px;
  width: 100px;
  background-color: green;
  height: 400px;
  z-index: 3;
}

#footer {
  position: fixed;
  z-index: 2;
  background-color: blue;
  width: 100%;
  height: 150px;
  bottom: 0;
}
<div id="header">
</div>
<div id="drop">
</div>
<div id="footer">
</div>

but is more elaborate in your actual case on Discourse, where the dropdown then can no longer be top: 100%; for nice positioning. You'd actually have to measure the correct y-coordinate when creating the dropdown, so this isn't a JS-free solution either.

Keep the dropdown in the header, but make the header not create a new stacking context

However, as explained above, this means that the header cannot be position: fixed anymore. And since your header must of course be fixed in the viewport (and we don't want to ensure this via JS), the header then has to have an ancestor that's position: fixed instead.

Just wrapping the header in another fixed-position div doesn't do you any good, since it creates the exact same problem; the footer is still in a different stacking context. So this wrapper div needs to include both the header and the footer. If that's not doable in Discourse's architecture, you can stop reading here.

By making your wrapper position: fixed, but making your header position: absolute and z-index: auto, you prevent a new stacking context from being created by the header.

The wrapper needs a width of 100%, so the header's and footer's width: 100% work. The wrapper will have a height of 0, which is both necessary (because it sits above your main content and thus must not obscure it) and not problematic (since the footer is still position: fixed at the bottom, and the wrapper itself has visible overflow).

In your example, it looks like this:

#fixed-wrapper {
  position: fixed;
  width: 100%;
}

#header {
  position: absolute;
  background-color: red;
  width: 100%;
  height: 150px;
  top: 0;
}

#drop {
  position: absolute;
  top: 5px;
  width: 100px;
  background-color: green;
  height: 400px;
  z-index: 3;
}

#footer {
  position: fixed;
  background-color: blue;
  width: 100%;
  height: 150px;
  bottom: 0;
}
<div id="fixed-wrapper">
    <div id="header">
      <div id="drop">
      </div>
    </div>

    <div id="footer">
    </div>
<div id="fixed-wrapper">

As for actually testing it on Discourse, I used http://discourse.opentechschool.org/ because it hasn't been updated yet to include your JavaScript workaround (and I happen to have an account there).

I clicked "reply" on a post, resized the composer so that it overlaps with the header, and clicked the (now only partially visible) "search" icon. As expected I couldn't see the search popup.

Then I put this into the JavaScript console:

$("<div id='fixed-wrapper' />").prependTo("#main")
    .css({position:"fixed", width:"100%", zIndex: 1000}) // z-index 1000 is what the
                                                         // header used to have

    .append(                                             // move the header and footer
        $("header.d-header, #reply-control")             // to the wrapper and remove
            .css({zIndex:"auto"})                        // their z-indexes
    );

$("header.d-header").css("position", "absolute")         // stop the header from
                                                         // creating a stacking
                                                         // context

Now the search box is above the composer, which itself is still above the header, as desired.

like image 193
balpha Avatar answered Sep 18 '22 16:09

balpha