Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

z-index behaviour is different in chrome to firefox

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.

like image 675
Thomas E. Avatar asked Jul 27 '15 07:07

Thomas E.


1 Answers

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>
like image 135
Hidden Hobbes Avatar answered Jan 04 '23 08:01

Hidden Hobbes