8

I am seeing a discrepancy in how fixed position elements behave within a relatively positioned parent. According to the docs I am finding online, FireFox and Chrome should fix the element to the viewport and not the parent. However, I am finding that if I do not specify a left/right value on a fixed element, it behaves in a sort of mix between static AND fixed, in the sense that it's fixed vertically to the viewport, but moves as if it were a static element within the parent element. I can't find any official/respected documentation surrounding these conditions. They all basically state something like the following:

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.

Source

Safari on the other hand, seems to render it as it is described where it's fixed purely to the viewport, no matter if I set a parent element to relative without any top/right/bottom/left properties defined. Try it out in Safari if you have a chance by clicking on the teal div which positions it -100 pixels from the left. The yellow bar will stay fixed to the viewport:

http://jsfiddle.net/bbL8Lh4r/2/

So which browser is rendering this correctly? All my browsers have been updated to the latest. At first I thought Safari was the right one just by reading the docs, but FireFox and Chrome both share the same different view where it seems to be a hybrid between static and fixed.


HTML

<body>
    <aside>
        Blah
    </aside>

    <div class="container">
        <div class="nav">
            BLARGH
        </div>
    </div>
</body>

CSS

body,
aside,
.container,
.nav {
    margin:0;
    padding:0;
}

aside {
    background:red;
    width:30%;
    height:800px;
    float:left;
}

.container {
    position:relative;
    height:800px;
    width:70%;
    background:teal;
    float:right;
}

.container.stickied {
    left:-100px;
}

.container .nav {
    position:fixed;
    background:yellow;
    width:inherit;
}
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Kerry Johnson
  • 842
  • 9
  • 16
  • Interestingly enough, if you explicitly define any of the box position properties, the element will no longer inherit the others. – Greg Rozmarynowycz Feb 18 '15 at 05:44
  • @GregRozmarynowycz Correct. That behavior is defined in the W3C spec regarding box offsets and the containing block of `fixed` elements. – TylerH Feb 18 '15 at 05:45
  • [CSS2.1](http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width) says that when calculating the static position of a fixed positioned element, the designated containing block is the initial containing block, not the viewport. (For some reason, I cannot find this same text in css-position.) It says that the initial containing block takes on the dimensions of the viewport, and seems to imply that this does not change even when content overflows the viewport, but it is not clear on that front. So perhaps the root of this question is: how is the *initial containing block* determined? – BoltClock Feb 18 '15 at 06:16
  • @BoltClock According to the research in my answer, we could say it *isn't determined* at all by the browsers in this case :-P – TylerH Feb 19 '15 at 14:25

2 Answers2

15

This appears to be an interesting case. Let's take a deep dive into the specifications to find out what's going on.


TL;DR: The W3 specification is critically vague/undefined in this area, but it appears that all browsers deviate from the spec, or at least, they made a decision where details were undefined. However, four of the major browsers (Firefox, Chrome, IE, and Opera) are united in that they all seem to deviate from the spec in the same way. Safari is definitely the odd man out here.


This is what the CSS2.1 spec has to say in Chapter 9: Visual formatting model:

  1. 9.1.2 Containing blocks - In CSS 2.1, many box positions and sizes are calculated with respect to the edges of a rectangular box called a containing block. In general, generated boxes act as containing blocks for descendant boxes; we say that a box "establishes" the containing block for its descendants. The phrase "a box's containing block" means "the containing block in which the box lives," not the one it generates.

This just defines what a containing block is.

  1. 9.3 Positioning Schemes - Absolute positioning: In the absolute positioning model, a box is removed from the normal flow entirely and assigned a position with respect to a containing block.

This says absolutely positioned elements are positioned with respect to a containing block.

  1. 9.6 Absolute Positioning - In the absolute positioning model, a box is explicitly offset with respect to its containing block. [...] References in this specification to an absolutely positioned element (or its box) imply that the element's position property has the value absolute or fixed.

This says absolutely positioned elements include position:fixed; elements as well as position: absolute; elements.

  1. 9.6.1 Fixed Positioning - Fixed positioning is a subcategory of absolute positioning. The only difference is that for a fixed position box, the containing block is established by the viewport.

And this says position: fixed; elements have the Viewport (well, not literally the viewport, but a box with the same dimensions and positions as the viewport) as their containing box. This is backed up later by the spec in 10.1 Definition of containing block:

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

(If you aren't familiar with what the viewport is, it is "a window or other viewing area on the screen through which users consult a document". The viewport's dimensions are the basis for the initial containing block. The entirety of your HTML content (<html>, <body>, etc.) resides within this initial containing block defined by the viewport.)

Therefore, the <div class="nav"> element with position: fixed; applied to it should have a containing block equal to the Viewport, or the initial containing block.


Now that the first step of determining the properties of the .nav element is complete, we can determine how the browsers are supposed to behave.

The CSS2.1 spec has this to say:

  1. 9.7 Relationships between 'display', 'position', and 'float' - Otherwise, if 'position' has the value 'absolute' or 'fixed', the box is absolutely positioned, the computed value of 'float' is 'none', and display is set according to the table below. The position of the box will be determined by the 'top', 'right', 'bottom' and 'left' properties and the box's containing block.

This is basically telling us that, for absolutely positioned elements (position: fixed; or position: absolute;), any float properties are ignored, that <div> elements (among others) are set to display: block;, and that the element is positioned according to its box offset values of top, right, bottom, and/or left in combination with the initial containing block (the viewport).

  1. 9.3.2 Box offsets: 'top', 'right', 'bottom', 'left' - An element is said to be positioned if its 'position' property has a value other than 'static'. Positioned elements generate positioned boxes, laid out according to four properties: top, right, bottom, left.

This just reaffirms the fact that <div class="nav"> should be positioned according to its box offsets.

Although it says in several places that if two opposing offset values are auto, then they are set to zero, CSS2.1 doesn't seem to specify the case for how to position elements with both left and right values of zero. CSS Box Alignment Module Level 3, however, does mention that the value is set to "start", which is defined as:

Aligns the alignment subject to be flush with the alignment container’s start edge.

This should mean the element is positioned at the top-left of the containing block, which, for position: fixed; elements, should be the same as the viewport. However, we can see that, for all major browsers, this is not the case. None of the major browsers seem to be setting the position: fixed;'s containing block to that of the viewport as instructed by the spec. Instead, they are all acting as if behavior should be identical between position: fixed; and position: absolute;.

In summation, when you have this much evidence in the spec's own words, the answer is clear: position: fixed; elements should have a containing block set to the viewport. What's also clear is that the vendors have all decided to fill-in a vague part of the spec in their own way, conflicting with, or outright ignoring this declaration. What is most likely to have happened is that one browser implemented their interpretation (IE7 was the first to support position: fixed;, I believe, followed shortly by Firefox 2.0) and the rest followed.

TylerH
  • 20,799
  • 66
  • 75
  • 101
  • Very thorough and provides the evidence for what I was leaning towards, although wasn't sure enough about it (and didn't have time to further research) to have my answer reflect it. This should be the accepted answer. – Greg Rozmarynowycz Feb 19 '15 at 14:49
  • I think this analysis may be incorrect: "For the purposes of calculating the static position, the containing block of fixed positioned elements is the initial containing block instead of the viewport, and all scrollable boxes should be assumed to be scrolled to their origin." See https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width – TomW Mar 13 '17 at 19:21
  • @TomW You think that quote might be incorrect? Or you think some part of the analysis is incorrect according to that quote? If the latter, which part specifically do you feel is incorrect; there's quite a bit of info here, pulled from different parts of the spec. – TylerH Mar 13 '17 at 19:46
  • @TomW For the record, the initial containing block is a block with the same position and dimensions as the viewport, as mentioned in my last few sentences above the horizontal rule in my answer. Suffice it to say, you can use the terms 'viewport' and 'initial containing block' interchangeably without any effect. – TylerH Mar 13 '17 at 19:56
  • @TylerH, actually, reading it again and following through your references again, I can't currently fault your analysis - sorry for the noise! The specs in the area of layout are very complex and somewhat ambiguous. I suspect the spec is meant to document what browsers are doing: that is, for fixed elements with no explicit positioning, position them in the same way as absolute elements. I can't see the browsers changing, which means the spec probably will (eventually). – TomW Mar 16 '17 at 11:01
  • 1
    @TomW Contrarily, the spec is meant to document what the law of the land is. In rare cases where the w3c or whatwg can't concur on something, they will defer to the eventual confluence of vendor choices, but the standard operating procedure is: the w3c mailing group decides something, puts it into the spec, and the vendors are expected to implement it. Of course, it helps that the w3c is made up of influential contributors from each vendor. – TylerH Mar 16 '17 at 13:57
2

Reading the W3C spec, I would say the behavior in Chrome/FF is actually correct:

Fixed Positioning

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

The absolute model positions relative to the containing block:

Absolute Positioning

The box's position (and possibly size) is specified with the top, right, bottom, and left properties. These properties specify offsets with respect to the box's containing block.

EDIT: For a fixed position element, the containing block is defined as the viewport:

If the element has 'positioned: fixed', the containing block is established by the viewport in the case of continuous media

However, in the case where all positing properties are set to auto, I couldn't find any definition of exactly what auto should result in. Therefore, the parent's position defines the initial position of the fixed element if no other position is given. In addition, the element does stay fixed with respect to the view port when scrolled, as specified. If the parent moves, the fixed element should move with it's initial position with it; the same as you would expect it to move if you changed the left property.

If it's incorrect to move the block with its parent, then it's incorrect to position it based on that parent in the first place. The only alternative is to position it in upper left hand corner of the viewport for properties of auto. If this is case, all of the browsers implement the spec wrong, Safari just has an implementation that's wrong and inconsistent.

It is worth noting that exhibited behavior occurs whether or not the parent element is relatively positioned.

Greg Rozmarynowycz
  • 2,037
  • 17
  • 20
  • This interpretation simply ignores the spec at [3.1](http://www.w3.org/TR/2015/WD-css3-positioning-20150203/#def-cb) among other places. " If the element has 'positioned: fixed', the containing block is established by the **viewport** in the case of continuous media or the page area in the case of paged media." The containing block is not the parent element for `position: fixed;` elements. – TylerH Feb 18 '15 at 14:39
  • I incorporated the 3.1 spec into my answer, see my edits – Greg Rozmarynowycz Feb 18 '15 at 15:44
  • "If it's incorrect to move the block with its parent, then it's incorrect to position it based on that parent in the first place." This seems a good point, but I remain unconvinced of either case entirely. I don't think the child is in its position from the parent because of `position: fixed`, though; I think it is in its place because of `float: right;`. I am taking another, more thorough dive through the spec and have asked Jonathan Sampson from the IE team to take a look in the hopes of providing a vendor perspective. – TylerH Feb 18 '15 at 19:26
  • "The only alternative is to position it in upper left hand corner of the viewport for properties of `auto`" Indeed, this example shows that's exactly how Safari implements it: http://jsfiddle.net/bbL8Lh4r/6/ Firefox, on the otherhand, will move the `.nav` element over to `left: -100px` but then keep it there after the `.stickied` class is toggled back off. Chrome keeps it within the `.container` parent at all times as does IE. So there is some definite inconsistency here. – TylerH Feb 18 '15 at 19:38