What Happens When Nesting Elements with Position: Fixed Inside Each Other

What happens when nesting elements with position: fixed inside each other?

The fixing and the positioning are two separate things. They're positioned the same as absolutely positioned elements: relative to their containing block. But in contrast with absolutely positioned elements, they remain fixed to that position with respect to the viewport (i.e. they don't move when scrolling):

http://www.w3.org/TR/CSS2/visuren.html#propdef-position

The box's position is calculated according to the 'absolute' model, but in addition, the box is fixed with respect to some reference.

Positioning

The definition of containing block says:

If the element has 'position: fixed', the containing block is established by the viewport in the case of continuous media (...)

and

If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed' (...)

which suggests that while their positioning algorithm is the same (they're both positioned relative to their containing block), the containing block for fixed elements is always the viewport, in contrast with absolutely positioned elements, so they should be positioned relative to that and not to any absolutely or fixed-positioned elements.

And as a matter of fact, that is indeed the case. For example, if you add top: 20px to .fixed, both divs will be positioned 20 pixels from the top of the viewport. The nested fixed div does not get positioned 20 pixels down from the top of its parent.

The reason you're not seeing that in this case is because you're not actually setting any of the left/top/right/bottom properties, so their positions are determined by the position they would have in the flow (their "static position"), which as my first quote said, is done according to the absolute model.

Position:fixed within position:fixed: which browser is correct?

To see two workarounds for the behavior you want, scroll down below the horizontal rule.


11/18/16 Update - The CSSWG got back to me and said that it should create a new stacking context:

You're right, this was supposed to be merged into the positioning spec as well - reflected now. Thanks.

  • New CSS Position specification diff

On the subject of which browser is correct:

fixed position elements should always be placed relative to the viewport, specifically that the position: fixed element's containing block is established "by the viewport" in 10.1.3:

If the element has 'position: fixed', the containing block is established by the viewport [...]

This containing block is formally called the "initial containing block".

9.3.1 also backs this up by saying that, for normal non-paged media (like this),

[...] In the case of handheld, projection, screen, tty, and tv media types, the box is fixed with respect to the viewport and does not move when scrolled.

What's happening in your code is that you are changing the value of the left property of the parent element on hover, and you are expecting the child element to move, too. However, the child element is (properly) not moving.

10.3.7 says

For the purposes of calculating the static position, the containing block of fixed positioned elements is the initial containing block instead of the viewport.

(static position here meaning the position of the element if it were placed in the normal flow).

It also says:

[If] 'left' and 'right' are 'auto' and 'width' is not 'auto', [...] set 'left' to the static position, otherwise set 'right' to the static position. Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').

This explains, I believe, why the child position: fixed element is initially set to left: -200px; per where it would be within its parent element if it were position: static.

At this point, it looks like you believe the parent's new left value should move the child element, I'm assuming, either because you expect the new left property to be inherited by the child (which is not how left works), or you expect it to re-flow the document, which doesn't happen on :hover as I recall; the browser only re-paints on :hover, which doesn't change the document flow, but does change the appearance of elements (e.g. background-color, opacity, visibility: hidden; etc).

So... elements on re-paint shouldn't move unless there are pseudo-selectors that change the properties during temporary states (like :hover), or transitions/animations at play.

In this situation, it appears that Chrome and Safari are doing something other than what the spec suggests; they are either causing a full re-flow, or they have set position: fixed elements to inherit left properties from ancestors. This appears to be above the board, if you will, according to the CSS Working Group draft linked by Oriol below. However, it's still non-standard behavior until the spec is updated.

  • Long-story short, Chrome and Safari are wrong right now, but eventually once the spec is updated, they will be correct, and Firefox will have to update its rendering behavior.

Make the .header div inherit your new left property, since that's how Chrome is doing it and that is the behavior you seek. I also adjusted .header's width just a bit, so that it won't cover the scroll bar on .wrapper:

.wrapper, .header {
position: fixed;
}

.wrapper:hover {
left:0px;
}
.wrapper{
width:320px;
height:100%;
background:white;
overflow:scroll;
left:-200px;
transition: all ease-out .3s;
}
ul {
margin-top:120px;
}
.header {
background:rgba(255,255,255,0.9);
left: inherit;
width: 303px;
}

body{
background:gray;
}
<div class="wrapper">
<div class="header">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repudiandae vitae a, itaque commodi, odio et. Excepturi, obcaecati? Architecto repellendus omnis mollitia animi rem quasi at, odit aperiam voluptatibus voluptates earum!
</div>
<ul>
<li>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium quam maiores, voluptas facere, iste quis iusto reiciendis delectus, quod blanditiis tempora. Earum voluptatum dicta quae, explicabo placeat at rerum assumenda!
</li>
<li>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium quam maiores, voluptas facere, iste quis iusto reiciendis delectus, quod blanditiis tempora. Earum voluptatum dicta quae, explicabo placeat at rerum assumenda!
</li>
<li>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium quam maiores, voluptas facere, iste quis iusto reiciendis delectus, quod blanditiis tempora. Earum voluptatum dicta quae, explicabo placeat at rerum assumenda!
</li>
<li>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium quam maiores, voluptas facere, iste quis iusto reiciendis delectus, quod blanditiis tempora. Earum voluptatum dicta quae, explicabo placeat at rerum assumenda!
</li>
</ul>
</div>

A fixed position child inside a relative parent renders not related to the parent in Chrome

Looks to me like you want the "absolute" position value instead of the "fixed" value, not sure why/when a change would have happened but according to w3c - Fixed: The element is positioned relative to the browser window. http://www.w3schools.com/cssref/pr_class_position.asp

http://jsfiddle.net/9q3v6/3/

 #parent-element{
border: 1px solid red;
height: 100px;
position: relative;
top: 50px;
width: 90%;
}
#child-element{
background-color: yellow;
height: 100px;
width: 100px;
position: absolute;
}

Cross-browser fix - position: fixed as descendants of transformed elements

I don't know of any "polyfills" to force cross browser consistency in this situation so, as it is presented as an acceptable alternative in the question, I'll provide an answer to the part about "feature detection".

You could test whether or not the browser complies with the spec by creating a couple of temporary elements, one nested inside the other. Use translatey() to move the parent element down from its "natural" position and fix the position of the child element, with a top value of 0. Then, by using getBoundingClientRect(), we can check whether or not the top of the child element is equal to that of the parent - if it is, the browser complies with the spec.

var body=document.body,

div=document.createElement("div"),

span=document.createElement("span"),

compliant;

div.style.transform="translatey(100px)";

span.style.position="fixed";

span.style.top=0;

div.appendChild(span);

body.appendChild(div);

compliant=span.getBoundingClientRect().top===div.getBoundingClientRect().top;

body.removeChild(div);

console.log(compliant);

Z-index not behaving as expected with absolute positioning inside a fixed position element

As Pete's comment alludes to, it all comes down to stacking contexts. In this case, both .fixed elements create their own stacking contexts by virtue of being position: fixed;. The child of the first .fixed element creates a stacking context nested within its parent. Because it's nested inside an existing stacking context, it can never break out and stack any higher; its z-index is relative to its parent now.

The spec is actually somewhat helpful with the particulars: http://www.w3.org/TR/CSS2/visuren.html#z-index. You can see via the numbered list that child stacking contexts are painted dead last.

So in your case, your .fixed.first element would need to have a z-index of 2 for its child to stack atop .fixed.second.

z-index with fixed elements

In short, without altering your markup, you can't do what you're after. The z-index of "middle" and "bottom" will take effect, but "top" will never appear on top of "middle". It's because "middle" and "bottom" are siblings - "top", as a child element, can't trump the z-index of it's parent.



Related Topics



Leave a reply



Submit