Can someone explain stacking contexts? - css

I recently had a bug where a video player wasn't clickable inside a jquery ui dialog.
I ended up fixing the problem by overriding the position:relative; with a position:inherit;
Other solutions to the problem included removing the position:relative; entirely or by making the z-index of the player class be something other than 1.
As I've read, these are both indicative of changing the stacking context in this situation which solved my problem. However, I still don't really understand what was going on in my situation or stacking contexts in general. Does anyone else have any good examples/suggesstions as to what may have been going on?
<div class="player">
<div id="videoPlayer_wrapper" style=" position: relative; width:580px; height: 192px;">
<object type="application/x-shockwave-flash" data="/flash/player.swf" width="100%" height="100%" bgcolor="#000000" id="videoPlayer" name="videoPlayer" tabindex="0">
</object>
</div>
</div>
where the CSS for player is
.player {
margin-bottom: 20px;
position: relative;
z-index: 1;
}

I found the article you linked by Phillip Walton to be most helpful to me understanding stacking contexts... I went on this study safari in the course of debugging an issue of my own.
Note that the pink square with z-index: 100; appears below the light blue square with z-index: 1; because it's constrained by a stacking context created on .A by the transform.
This jsbin is a bit easier to experiment with than the SO inline code: https://jsbin.com/lataga/2/edit?css,output
div {
width: 200px;
height: 200px;
padding: 1rem;
}
.A {
position: absolute;
background-color: red;
/*
Adding a transform here creates a
new stacking context for the children of .A
*/
transform: translateX(0);
/*
several other properties can trigger creation of stacking context,
including opacity < 1
*/
/* opacity: 0.99; */
/*
If we raise .A above .B, the children will rise up with it;
uncomment the following to see:
*/
/* z-index: 3; */
}
.a {
position: relative;
/*
even a much higher z-index can't lift .a above .b when
it is constrained to a lower stacking context
*/
z-index: 100;
margin-left: 125px;
background-color: pink;
}
.B {
position: absolute;
margin-top: 75px;
/* z-index: 2; */
background-color: blue;
}
.b {
margin-left: 50px;
background-color: lightblue;
/*
The following is not necessary: if a z-index is not specified,
then it is calculated according to the rules of
natural stacking order...
I'm just specifying it explicitly for editorial effect.
*/
z-index: 1;
}
<div class="A">
A: 1
<div class="a">a: 1.100</div>
</div>
<div class="B">
B: 2
<div class="b">b: 2.1</div>
</div>
Stacking Context
Positioning and assigning a z-index value to an HTML element creates a stacking context, (as does assigning non-full opacity).
Stacking contexts can be contained in other stacking contexts, and together create a hierarchy of stacking contexts.
Each stacking context is completely independent from its siblings: only descendant elements are considered when stacking is processed.
Each stacking context is self-contained: after the element's contents are stacked, the whole element is considered in the stacking order of the parent stacking context.
Note: The hierarchy of stacking contexts is a subset of the hierarchy of HTML elements, because only certain elements create stacking contexts. We can say that elements that do not create their own stacking contexts are assimilated by the parent stacking context.
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
Put differently
Every stacking context has a single HTML element as its root element. When a new stacking context is formed on an element, that stacking context confines all of its child elements to a particular place in the stacking order. That means that if an element is contained in a stacking context at the bottom of the stacking order, there is no way to get it to appear in front of another element in a different stacking context that is higher in the stacking order, even with a z-index of a billion!
...
[...] In addition to opacity, several newer CSS properties also create stacking contexts. These include: transforms, filters, css-regions, paged media, and possibly others. As a general rule, it seems that if a CSS property requires rendering in an offscreen context, it must create a new stacking context.
http://philipwalton.com/articles/what-no-one-told-you-about-z-index/

Related

Why is a textnode rendered below its parents` ::before by default?

On writing-up an answer for a different question on SO, I made this snippet:
#import url('https://fonts.googleapis.com/css?family=Shadows+Into+Light');
/* relevant CSS */
div {
position: relative;
}
div::before {
content: '';
position: absolute;
top: 0; left:0;
}
div>span {
position:relative;
z-index:0;
}
/* rest is just styling - should be irrelevant for the question */
div {
font: normal normal normal 2rem/1 'Shadows Into Light', cursive;
color: white;
text-align:center;
margin: 1rem;
padding: 1rem;
min-width: 15rem;
cursor: pointer;
}
div::before {
width: 100%;
height: 100%;
opacity: 1;
transition: opacity .3s cubic-bezier(.4,0,.2,1);
background-color: #bada55;
border-radius: .4rem;
}
div[orange]:before {
background-color: #f50;
}
div:hover::before {
opacity:.65;
}
body {
margin: 0; padding: 0;
}
center-me-please {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
background: transparent url(http://lorempixel.com/g/1200/800/fashion) no-repeat 50% 50% /cover;
}
<center-me-please>
<div><span>#bada55</span></div>
<div orange>not so #bada55</div>
I was surprised to notice the ::before element is rendered above textnodes (orange element) and, in order to prevent it from happening. I had to wrap the textnode in a span element and give it a non-negative z-index and a non-static position (#bada55 element).
On inspection, while the ::before element has a default (expected) value of z-index (auto), the textnode doesn't seem to have one at all (or at least Chrome's not able to show it).
Up to now I liked to think of myself as a z-index little ninja, idea partly backed-up by developing this toy to help friends and colleagues better understand stacking contexts principle and z-index in general.
As you might have guessed, I'm looking for any explanation on why ::before is not rendered below everything else in an element by default (it's first, therefore below, right?) and for any evidence about this being either a (known?) bug or intended (by design?) behavior.
A spec I might have missed or misinterpreted would be great.
::before is painted below text content by default — and the default case is when everything is non-positioned.
But your ::before is absolutely positioned. Positioned boxes are always painted in front of non-positioned boxes. Refer to section 9.9.1 (emphases mine):
Within each stacking context, the following layers are painted in back-to-front order:
the background and borders of the element forming the stacking context.
the child stacking contexts with negative stack levels (most negative first).
the in-flow, non-inline-level, non-positioned descendants.
the non-positioned floats.
the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
the child stacking contexts with positive stack levels (least positive first).
Wrapping your text content in a positioned span causes it to be painted in front of the ::before content as expected since then you have two positioned boxes in source order.

When/How does z-index break stacking context? [duplicate]

This question already has an answer here:
Understanding z-index stacking order
(1 answer)
Closed 6 years ago.
My understanding is that z-index elements are only stacked with their siblings. I've also read that z-index "doesn't work" with position: fixed. However, I'm using the two together and it happened to do exactly what I wanted it to; and I want to understand why.
I have a blocking div that needs to cover everything in the page except one element. The element is not a sibling of the blocker, which I thought would make it impossible, but it worked. What rule is being exploited here, and how can I predict if it will work in other browsers?
.top {
background-color: yellow;
}
.yes {
position: relative;
z-index: 10;
background-color: blue;
}
.no {
position: relative;
z-index: 1;
background-color: red;
}
.blocker {
position: fixed;
z-index: 5;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .7);
}
<div class="top">
<button class="yes">
Yes
</button>
<button class="no">
No
</button>
</div>
<div class="blocker">
</div>
Every non-positioned box belongs to the same stacking context that's established by the closest ancestor that meets any of the criteria listed here, or the root element if there is no such ancestor. This means that elements don't have to be siblings of one another in order to participate in the same stacking context. They may share a distant ancestor (in HTML, every element descends from the root element), but they don't have to share a parent.
.top does not establish a new stacking context for its descendants .yes and .no as it is non-positioned and its z-index is auto. Therefore, .yes, .no and .blocker all participate in the same stacking context — the root stacking context. As a result the mere fact that .yes has a higher z-index than .blocker causes it to be painted in front.
Note that while each of .yes, .no and .blocker does establish its own stacking context, the stacking contexts they establish are not pertinent here; it's the stacking context that they participate in, and they all participate in the same root stacking context.

css property will-change undefined behaviour

I am using semantic-ui, and have managed to narrow down some undefined behaviour in the css property will-change (I found it in their modal's):
.outer{
background-color: black;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.inner{
position:absolute;
background-color: white;
left: 50%;
top: 100px;
width: 400px;
margin-left: -200px;
height: 100px;
padding: 5px;
/**
* comment out the line below
* to see the desired/different result
**/
will-change: transform;
}
.baby{
color: yellow;
position: fixed;
left: 20px;
top: 20px;
right: 0;
border: 1px solid red;
}
<div class="outer">
<div class="inner">
<div class="baby">here</div>
<div class="content">some content</div>
</div>
</div>
I have only tested this in chrome. Does anyone have more information on what is going on here? Why does will-change do anything to the actual layout?
will-change affects layout because it's often used with properties whose values can change between one that doesn't affect layout, and one that does. Setting will-change tells the browser to prepare for such a potential change, which results in the browser applying the layout changes in advance.
This isn't undefined behavior:
If any non-initial value of a property would create a stacking context on the element, specifying that property in will-change must create a stacking context on the element.
If any non-initial value of a property would cause the element to generate a containing block for absolutely positioned elements, specifying that property in will-change must cause the element to generate a containing block for absolutely positioned elements.
If any non-initial value of a property would cause the element to generate a containing block for fixed positioned elements, specifying that property in will-change must cause the element to generate a containing block for fixed positioned elements.
If any non-initial value of a property would cause rendering differences on the element (such as using a different anti-aliasing strategy for text), the user agent should use that alternate rendering when the property is specified in will-change, to avoid sudden rendering differences when the property is eventually changed.
For example, setting opacity to any value other than 1 creates a stacking context on the element. Thus, setting will-change: opacity also creates a stacking context, even if opacity is currently still equal to 1.
In your case, since transforms result in the creation of both a stacking context and a containing block, setting will-change: transform will therefore also result in the creation of a stacking context and a containing block, because you're suggesting to the browser that the element might have a transform either now or later, and when it does, the layout will be affected.

Override CSS Z-Index Stacking Context

I'm trying to override / ignore the stacking context for an element so it can be positioned on the z-axis relative to the page root.
However, according to the article What No One Told You About Z-Index:
If an element is contained in a stacking context at the bottom of the stacking order, there is no way to get it to appear in front of another element in a different stacking context that is higher in the stacking order, even with a z-index of a billion!
New stacking contexts can be formed on an element in one of three ways:
When an element is the root element of a document (the element)
When an element has a position value other than static and a z-index value other than auto
When an element has an opacity value less than 1
With the following example:
.red, .green, .blue { position: absolute; }
.red { background: red; }
.green { background: green; }
.blue { background: blue; }
<div><span class="red">Red</span></div>
<div><span class="green">Green</span></div>
<div><span class="blue">Blue</span></div>
If the first div is given opacity:.99;, (which creates a new stacking context on the first node) then even if .red has z-index:1, it will still be placed behind the other elements because it is just rendered as the highest element within that stack.
Working Demo in jsFiddle
Which looks like this:
Q: Is there a way for an element to ignore the stack context of any of it's parent elements and ask to be positioned relative to the original stack context of the page?
Q: Is there a way for an element to ignore the stack context of any of it's parent elements and ask to be positioned relative to the original stack context of the page?
No, it's not possible to transfer a positioned element between stacking contexts without repositioning the element in the DOM. You cannot even move an element to the root stacking context by using position: fixed or position: absolute (as you have observed, .red is being positioned relative to its parent, div:first-child because it creates a new stacking context).
That being said, given your HTML and CSS it should be trivial to just reassign the classes to the div elements instead, as shown in other answers and here so all your divs and spans participate in the root stacking context:
<div class="red"><span>Red</span></div>
<div class="green"><span>Green</span></div>
<div class="blue"><span>Blue</span></div>
But your situation probably isn't as simple as it seems.
We can do it using 3D transformation and we will be able to bring any element to the front even if it's trapped inside a stacking context:
.red,
.green,
.blue {
position: absolute;
width: 100px;
color: white;
line-height: 100px;
text-align: center;
}
body,
div:first-child {
transform-style: preserve-3d; /* this is important for the trick to work */
}
.red {
top: 20px;
left: 20px;
background: red;
/*z-index: 1; we no more need this */
transform:translateZ(1px); /* this will do the trick */
}
.green {
top: 60px;
left: 60px;
background: green;
}
.blue {
top: 100px;
left: 100px;
background: blue;
}
<div><span class="red">Red</span></div>
<div><span class="green">Green</span></div>
<div><span class="blue">Blue</span></div>
More details and examples here: Why can't an element with a z-index value cover its child?
As it stated in the The stacking context: "Using z-index, the rendering order of certain elements is influenced by their z-index value. This occurs because these elements have special properties which cause them to form a stacking context.
To partly overcome stacking content problem you can use css properties to display unwanted elements:
opacity: 0.1;
or
display: none;

Pseudo element on parent hidden behind child image on IE8

Why in IE8, is the background color of a pesudo element flowing behind children of the parent? The text flows in front, but the background-color does not. Z-index did not seem to help any.
I haven't been able to determine if this is a bug in IE8 or not. It seems like this would have been a pretty common use-case, but I couldn't find many blog posts or SO questions related to it.
http://jsfiddle.net/VAg2E/
<div id="parent">
<img src="http://placehold.it/200x200">
</div>
#parent{ padding: 20px; }
#parent:before{
content: 'Behind the image';
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: red;
}
Edit : A related Stack Overflow Question about Stacking Order
This is definitely a bug in IE8; since your :before pseudo-element is positioned, it should create a new stacking context and always be drawn on top of the img unless you give it a negative z-index (even then, the entire element should be drawn behind it, not just its background).
This issue also seems specific to stacking between :before and :after pseudo-elements and replaced elements like img. It looks like IE8 is treating replaced content differently in terms of stacking, but whatever it is doing, it's definitely not conforming to the spec.
As you're probably aware, this is fixed in IE9.
Have your exact same issue, the only thing you can do is force the stacking order via CSS and z-index. The only catch is that z-index is placed on child element starting from parent element, so you wont be able to do a proper logic order as #parent-element {z-index: 2} and #child-element {z-index: 1}, the z-index for the #child-element will just be set to level 1 as a separate stack order inside the #parent-element.
You can still set z-index for the #child-element with a -1 value, it will just get back the whole #parent-element stacking order.
So to recap:
#parent-element { z-index: 99;} /* or any arbitrary number fitting */
#child-element {z-index: -1;}
Also remember to give both elements a position: relative/absolute to enable the stacking order fo z-index
IE8 only supports pseudos if <!DOCTYPE> is declared. Source
#parent { padding: 20px; z-index: 2; }
#parent:before {
content: 'Behind the image';
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: red;
z-index: -1;
}​

Resources