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
?
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:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With