12

I have a bunch of CSS that is applied to a parent element and its children:

.parent {
  position: fixed;
  top: 0px;
}
.el {
  position: fixed;
  top: 5px;
  z-index: 100;
}
.bodycontent {
  z-index: 1;
  position: relative;
}
<div class="parent">
  <div class="el">
    <button></button>
  </div>
</div>
<div class="bodycontent"></div>

The page is made so that when it is scrolled, .parent goes underneath .bodycontent but .el goes above it. This works how I want it to in Firefox but not on Chrome.

Any suggestions? I have tried messing around with different z-index values and different position values with no success.

Hidden Hobbes
  • 13,893
  • 3
  • 38
  • 64
Thomas E.
  • 521
  • 2
  • 6
  • 13
  • You didn't define the z-index in parent.... and use absolute position for el. – Bhojendra Rauniyar Jul 27 '15 at 07:01
  • defining the z-index has no desired effect. – Thomas E. Jul 27 '15 at 07:03
  • So is bodycontent meant to go underneath parent and el ? – deansimcox Jul 27 '15 at 07:14
  • parent is supposed to go under bodycontent, el to go above – Thomas E. Jul 27 '15 at 07:27
  • I believe that constructions like that were never meant to work the way you want to. Either bodycontent goes completely under, or completely over, parent. Not in between parent and its descendants. So Chrome is working as expected, Firefox has got it wrong. Redesign your markup. – Mr Lister Jul 27 '15 at 08:25
  • Good question though, it had me puzzling too for a while. For people wanting to try, [here](http://jsfiddle.net/MrLister/tbf74114/) is a fiddle that demonstrates the issue. – Mr Lister Jul 27 '15 at 08:27
  • Yeah, I thought it was an issue like that :/ unfortunate, I thought that I would be able to remove it from the HTML flow of the document and layer it. – Thomas E. Jul 27 '15 at 09:04

1 Answers1

22

Both Chrome and Firefox are working as intended

From version 22 onwards, this is the way Chrome intentionally handles the stacking of fixed elements. As stated in an article by Google themselves:

In Chrome 22 the layout behavior of position:fixed elements is slightly different than previous versions. All position:fixed elements now form new stacking contexts. This will change the stacking order of some pages, which has the potential to break page layouts.

(https://developers.google.com/web/updates/2012/09/Stacking-Changes-Coming-to-position-fixed-elements?hl=en)

Firefox is working as it intends too. The Mozilla docs state that this behaviour is localised to mobile WebKit and Chrome 22 onwards:

on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto"

(https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context)

Why this happens

The result of this change means that Chrome will always create a new stacking context even if the z-index of the parent container is set to auto (the default). This differs from position: absolute; and position: relative; as they only form their own stacking context when z-index is not set to auto.

Most elements on a page are in a single, root stacking context, but absolutely or relatively positioned elements with non-auto z-index values form their own stacking contexts (that is, all of their children will be z-ordered within the parent and not be interleaved with content from outside the parent). As of Chrome 22, position:fixed elements will also create their own stacking contexts.

(https://developers.google.com/web/updates/2012/09/Stacking-Changes-Coming-to-position-fixed-elements?hl=en)

The effect of this means that in your example .el's z-index is computed relatively to its parent, .parent. It is displayed under .bodycontent because:

  • .bodycontent's z-index is relative to the root
  • .el's z-index is relative to .parent
  • .parent's z-index is relative to the root
  • .parent's z-index is not specified so it is set to the default auto (in effect, 0)
  • .parent has a lower z-index than .bodycontent and is therefore displayed under it. Because .el belongs to it, it too is displayed under .bodycontent.

Example of expected results

body {
  margin: 0;
}
div {
  height: 100px;
  width: 100px;
}
.parent {
  background-color: red;
  position: fixed;
  top: 0;
}
.el {
  background-color: blue;
  left: 25px;
  position: fixed;
  top: 25px;
  z-index: 100;
}
.bodycontent {
  background-color: green;
  left: 50px;
  position: relative;
  top: 50px;
  z-index: 1;
}
<div class="parent">
  <div class="el"></div>
</div>
<div class="bodycontent"></div>

The above code will produce the following results in Chrome and Firefox:

Example result in Chrome and Firefox

Which is right?

It would appear that Chrome is not following the W3C spec and that this change was made so that the desktop implementation matched the mobile implementation:

Mobile browsers (Mobile Safari, Android browser, Qt-based browsers) put position:fixed elements in their own stacking contexts and have for some time (since iOS5, Android Gingerbread, etc) because it allows for certain scrolling optimizations, making web pages much more responsive to touch. The change is being brought to desktop for three reasons:

1 - Having different rendering behavior on “mobile” and “desktop” browsers is a stumbling block for web authors; CSS should work the same everywhere when possible.

2 - With tablets it isn’t clear which of the “mobile” or “desktop” stacking context creation algorithms is more appropriate.

3 - Bringing the scrolling performance optimizations from mobile to desktop is good for both users and authors.

Firefox is handling the stacking in the correct way.

How to get the desired result

The only way this behaviour can be circumvented is to move .el out of .parent and instead make it a sibling:

body {
  margin: 0;
}
div {
  height: 100px;
  width: 100px;
}
.parent {
  background-color: red;
  position: fixed;
  top: 0;
}
.el {
  background-color: blue;
  left: 25px;
  position: fixed;
  top: 25px;
  z-index: 100;
}
.bodycontent {
  background-color: green;
  left: 50px;
  position: relative;
  top: 50px;
  z-index: 1;
}
<div class="parent"></div>
<div class="el"></div>
<div class="bodycontent"></div>
Community
  • 1
  • 1
Hidden Hobbes
  • 13,893
  • 3
  • 38
  • 64
  • Interesting read, for me I moved el so that it is no longer a child. Thanks for all the references. – Thomas E. Jul 30 '15 at 00:33
  • 1
    In 2022 I get the expected result in both Chrome and FX on the first code and the unexpected result in both browsers on the second code – mplungjan Feb 16 '22 at 11:55