Position:fixed within position:fixed: which browser is correct?
Asked Answered
A

2

11

Positioning a fixed element inside another fixed element is behaving differently in Chrome / Safari vs Firefox.

This answer explains well the expected behavior for a fixed div inside a relative one, and MDN is pretty clear on this:

Fixed Positioning Do not leave space for the element. Instead, position it at a specified position relative to the screen's viewport and don't move it when scrolled. When printing, position it at that fixed position on every page.

What I don't understand is what Firefox is doing with a fixed div inside a fixed div. What I expect is that the child element moves along with the wrapper on hover.

.wrapper, .header {
  position:fixed;
  width:320px;
}

.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);
}

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>

Any thoughts? I'm looking for a workaround to have consistency across browsers, too.

Edit : more fun?

Add this to glitch it even more on FF :

.header:hover{
  height:200px;
}

On hover, it triggers a repaint, then FF recalculate the position of the element.

Tests made with FF 46.0.1, Chrome 54.0.2840.71 and Safari Version 9.1.1 (11601.6.17). Note: I already read this question

Angevin answered 11/11, 2016 at 15:24 Comment(11)
That's the problem with browsers, to some extent they're all different, like different dialects of the same language, a Scot uses different English words from a Welshman. So while there are different browsers I think there will always be these sort of edge-case differences, no one is right or wrong as such, they're just different. Raise a flag with Mozilla (esp. if they're a minority browser behaviour wise) and it'll get noted, fixed, and made consistent... eventuallyRivers
@Rivers Will do. But in meantime, I'm looking for a workaround : I have full control over markup, CSS and even JS if really needed. I've attempted several combination of CSS rules, nothing fits : I absolutely need my sidebar to scroll overflowing content, as in the snippet, and it kills a lot of alternatives.Angevin
I don't understand the expected behavior, could you please rephrase that? Is chrome the one browser rendering the way you want?Antirrhinum
I don't understand why you need position:fixed in position:fixed. Doesn't position:absolute in position:fixed get you what you want?Syncopated
@Syncopated Unfortunately, no: I need the .header to stay visible when I scroll the sidebar. With position:absolute, it scrolls with the content.Angevin
@JonasGrumann Sorry for the late reply : yes, Chrome renders it as I expect!Angevin
@Bigood - I see. Then @TylerH's analysis is correct and the problem is that Chrome is incorrectly inheriting the left property onto the header div. To make Firefox behave the same, explicitly set the left property to inherit. i.e. Add to your CSS .header { left:inherit; }Syncopated
@Syncopated Thanks for your workaround, this works perfectly! Please make it an answer so I can upvote it.Angevin
Your edit is unclear; every browser repaints on :hoverFitzwater
@Fitzwater For sure: I'm highlighting the recomputing of the position when we hover specifically the .header.Angevin
@Bigood Can you make a gif of what you're seeing? I don't notice any visual or performant differences between Firefox & Chrome when I add that line of CSSFitzwater
F
4

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.


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>
Fitzwater answered 11/11, 2016 at 16:30 Comment(9)
I initially thought the same but I discovered that its due to bug in Firefox about how it handles the :hover. See my answer.Orate
I don't trust the spec. The CSS WG is resolving things about positioning and doesn't update the spec. See github.com/w3c/csswg-drafts/issues/451. I vaguely remember they decided to change this too to behave like Chrome, but I'm not sure.Slily
@Orate No, see my comment on your answer. Offsets are not inherited.Fitzwater
@Slily If that's the case well have to wait for the spec to be updated. I've added a comment to that Github issue.Fitzwater
If the problem is that position:fixed;z-index:auto; doesn't create a new stacking context, then I would expect that one solution to the question would be to set the z-index of the wrapper div to a number. But that does not in fact solve the problem.Syncopated
@Fitzwater Thank you for your detailed answer ! Unfortunately, you workaround changes the desired behavior, as the .header moves along the sidebar…Angevin
@Bigood Ah, you wanted the div fixed on scroll, got it. I've updated the solution to get the behavior you're looking for. Give it a try!Fitzwater
@Fitzwater Thanks, they ignored me. So fixed elements establish a stacking context even with z-index: auto. What I'm not sure is if they also establish a containing block for fixed descendants or not, that's what would be relevant here.Slily
@Slily Agreed, I talked about it briefly with Boltclock previously and we were both unsure how establishing a new stacking context would affect this. I'll keep ruminating over this.Fitzwater
O
-1

So, I think problem has arisen due to the bug in implementation of left in Firefox.

When hovering .wrapper, .header should get new left value from its parent .wrapper i.e. 0px. When hovering .wrapper, the left position of .header should be calculated using the left value from its parent .wrapper as no explicit left value is given to .header.

I think its due to bug in Firefox. If you activate :hover pseudo class of .wrapper using Firebug or the default developer tool, the left position of .header is maintained like in Chrome (but in sudden manner).

Tested on Firefox 49.0.2 and Chrome 54.0.2840.71

Orate answered 11/11, 2016 at 16:42 Comment(6)
Box offsets (left, right, top, and bottom) are not inherited. See w3.org/TR/CSS22/visuren.html#position-props Even if Chrome were right and Firefox wrong, it would not be because of this.Fitzwater
I am not implying that box offsets are inherited. As the default value of left is auto, .header has left: auto too. The auto value is, after all, resolved to some absolute value by the both the browser.But in Firefox, when hovering the .wrapper, the .header is not getting the updated value, which I have thought be be bug in Firefox.Orate
You're right, ".header should get new left value from its parent .wrapper" is you not implying, but explicitly saying it should be inherited. .header's left value is not declared to start with or declared to change on hover in OP's code, so it should never change.Fitzwater
left does not determine the offset with respect to the parent. It's with respect to the containing block.Slily
@Slily I mentioned that the .left is calculated from the value from the parent, not that it determine the offset with respect to the parent.Orate
Then I agree with TylerH, you are explicitly saying left should be inherited.Slily

© 2022 - 2024 — McMap. All rights reserved.