Why is an element with position: fixed moving with a non-positioned sibling?
Asked Answered
S

1

25

There are a lot of questions on S.O. that cover the answer to how to fix this (add top: 0), but none of them attempt to actually explain the reasoning behind the header movement. I'm more curious as to why this is the case.

<header>Project Header</header> 
<main class="container" id="layout-mainContent">
    <div class="row" id="first-row">somecontent</div>
</main>

header {
   position: fixed;
}

#layout-maincontent {
   margin-top: 90px;  //moves header down.
}

List of like-questions but with no reasoning:

  1. Topmost 'fixed' position div moving with non position div
  2. margin affects other fixed elements position
  3. margin-top div causes fixed header div to move down

It seems reasonable to think that the fixed header sticks to the top of the browser window and should not move because of another non-positioned, non-child, non-parent div (aka sibling). Esp. because the fixed header is outside of normal document flow. MDN on Fixed Positioning

Hypothesis: The confusion stems from the idea that fixed elements are relative to the browser window. This is true, but is calculated using the viewport. The viewport is calculated using elements that are within the regular document flow. Because the first div that is within document flow is the non-header div, the viewport starts after the margin-top is applied. This is just speculation and I would love to see someone confirm or correct me.

Sesquicentennial answered 31/7, 2016 at 1:22 Comment(1)
I was really wondering why adding position: fixed suddenly shifted my header downwards, thanks a lot for the tip on using top: 0!Horwitz
C
31

With position: fixed, your header element is removed from the document flow.

The first in-flow element is main, which has margin-top: 90px in your code.

The parent of this element is body, which normally has a default margin: 8px (see HTML default style sheet).

Due to CSS margin collapsing, the body element's margin-top: 8px is collapsed with the main element's margin-top: 90px.

As a result, both elements, now having the same margin, shift down 90px.

html {
    background-color: green;
    height: 100%;
 }

body {
    background-color: pink;
    height: 100%;
}

header {
   position: fixed;
   border: 1px solid red;
}

main {
  margin-top: 90px;
  background-color:yellow;
}
<header>Project Header</header> 
<main class="container" id="layout-mainContent">
    <div class="row" id="first-row">somecontent</div>
</main>

jsFiddle

The reason the fixed header moves is as follows:

  • Although the containing block for an element with position: fixed is the viewport...
  • The CSS offset properties (top, bottom, left and right) have an initial value of auto, which keeps the element where it normally would be if it were in the document flow.
  • Said another way, when you set an element to position: absolute or position: fixed (another form of position: absolute), you're specifying the type of positioning you want... but you're not positioning it anywhere.
  • It isn't until you define the offsets that the element is actually positioned.
  • To shift the header to the top of the viewport, use top: 0.

html {
    background-color: green;
    height: 100%;
 }

body {
    background-color: pink;
    height: 100%;
}

header {
   position: fixed;
   border: 1px solid red;
   top: 0px; /* NEW */
}

main {
  margin-top: 90px;
  background-color:yellow;
}
<header>Project Header</header> 
<main class="container" id="layout-mainContent">
    <div class="row" id="first-row">somecontent</div>
</main>

jsFiddle

Carbarn answered 31/7, 2016 at 1:37 Comment(4)
assume that body is an immediate ancestor/container of header and main. Using display: flow-root on body will put your header at the top and main below it. Can you elaborate on this a little bit?Uganda
what about setting overflow:auto on the parent container to also start a new block formatting context?Transilient
@Uganda margins collapse only for boxes that belong to the same block formatting context. When you set display: flow-root you create new block formatting context, so margin of parent and child elements don't collapse.Shrink
@Transilient when you set 'auto' for overflow property on the body, that value propagates to the viewport and the body still uses initial 'visible' value. To stop propagation and really set other than initial 'visible' value for overflow on the body you need to do the same for html element. drafts.csswg.org/css-overflow-3/#overflow-propagationShrink

© 2022 - 2024 — McMap. All rights reserved.