Why doesn't top: 0 work on absolutely-positioned elements relative to body?
Asked Answered
K

5

17

You can see in the code below that the h1 pushes down the body and the absolutely-positioned block .absolute does not stick to the top. But you also can see that the same block is stuck to the top of its parent .wrapper. Why?

I'm not asking how to do that trick; I know how, e.g. padding instead margin to h1, or clearfix to parent and so on.

I'm interested in only one thing: why h1's margin pushes down the body, but is not pushing down .wrapper?

body {
  position: relative;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background-color: silver;
}

.absolute {
  position: absolute;
  top: 0;
  left: 0;
  width: 50px;
  height: 50px;
  background-color: darkblue;
}

.wrapper {
  position: relative;
  overflow:hidden;
  width: 50%;
  height: 200px;
  overflow: hidden;
  background-color: yellow;
}

.wrapper > .absolute {
  background-color: darkcyan;
}
<div class="absolute"></div>
<h1>Some title</h1>

<div class="wrapper">
  <div class="absolute"></div>
  <h1>Some title</h1>
</div>

OK, I'll try to be more clear. If you click on the link below, you can see my JSFiddle. There is background-color: silver in the body tag. When I look in the code inspector, I see that the body tag begins slightly lower due to the h1 margin. But background-color begins at the top. This means that the code inspector is lying to me, and body begins from top. But why, then, does an absolutely-positioned element that's a direct child of body not start at the top?

JSFiddle

Kassi answered 8/11, 2015 at 14:2 Comment(5)
Possible duplicate of CSS margin pushing the body elementTia
@Kassi OK I got your answer, see my post.Illuminative
Not a duplicate - that question doesn't involve any absolutely positioned elements.Palp
Possible duplicate of Why does making the body tag's style "position: relative" cause an absolute-positioned div start lower?Florrie
@DonRhummy, as I commented in your post, yes, the questions are similar and probably duplicates. But the answers are an ocean apart. Your question didn't receive any quality answers. In fact, one of the answers to your question points this out. This question, however, received a clear and complete explanation, plus other valuable insights. Hence, it wouldn't be helpful to users to re-direct this question to yours.Bagley
P
13

As mentioned, the containing block of the top-level absolutely positioned element is body, as body is relatively positioned. When the margin of the h1 collapses with that of body, this causes the margin to affect body, and in turn the absolutely positioned element that it contains. Conversely, if body wasn't relatively positioned, the absolutely positioned element would be anchored to the initial containing block, and stick to the top of the viewport unaffected by any effective margins on body (because body is no longer its containing block).

As to why the silver background bleeds beyond the body element, that's by design:

3.11.1. The Canvas Background and the Root Element

The background of the root element becomes the background of the canvas and its background painting area extends to cover the entire canvas. However, any images are sized and positioned relative to the root element as if they were painted for that element alone. (In other words, the background positioning area is determined as for the root element.) The root element does not paint this background again, i.e., the used value of its background is transparent.

3.11.2. The Canvas Background and the HTML <body> Element

For documents whose root element is an HTML HTML element or an XHTML html element: if the computed value of ‘background-image’ on the root element is ‘none’ and its ‘background-color’ is ‘transparent’, user agents must instead propagate the computed values of the background properties from that element's first HTML BODY or XHTML body child element. The used values of that BODY element's background properties are their initial values, and the propagated values are treated as if they were specified on the root element. It is recommended that authors of HTML documents specify the canvas background for the BODY element rather than the HTML element.

The root element's default background color is always transparent, so setting a background color on the html element prevents the silver background from bleeding out of the body element (and you'll see that the inspector is in fact not lying to you):

html {
  background-color: white;
}

body {
  position: relative;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background-color: silver;
}

.absolute {
  position: absolute;
  top: 0;
  left: 0;
  width: 50px;
  height: 50px;
  background-color: darkblue;
}

.wrapper {
  position: relative;
  overflow:hidden;
  width: 50%;
  height: 200px;
  overflow: hidden;
  background-color: yellow;
}

.wrapper > .absolute {
  background-color: darkcyan;
}
<div class="absolute"></div>
<h1>Some title</h1>

<div class="wrapper">
  <div class="absolute"></div>
  <h1>Some title</h1>
</div>
Palp answered 8/11, 2015 at 18:10 Comment(2)
@Quantastical: I just woke up and holy upvotes Batman... this must have made hot network questions or something. Who said no one cares?Palp
@Palp The votes are from the committee tasked with illustrating how much no one cares <3Portraitist
R
8

It's because collapsing margins. How to disable it, you could read in some articles/answers, for example, this.
You are using overflow: hidden for .wrapper, and it disables collapsing margins for this block. From the specification:

Margins of a box with ‘overflow’ other than ‘visible’ do not collapse with its children's margins.

But it seems like overflow: hidden doesn't work for <body>, because if I set
height: 0/max-height: 0 it doesn't work too:

body {
    height: 0;
    max-height: 0;
}
<div>Some test text</div>

I think it's because (from the specification):

UAs must apply the 'overflow' property set on the root element to the viewport. When the root element is an HTML "HTML" element or an XHTML "html" element, and that element has an HTML "BODY" element or an XHTML "body" element as a child, user agents must instead apply the 'overflow' property from the first such child element to the viewport, if the value on the root element is 'visible'.

That's why margins between <body> and first <h1> collapse (from MDN):

Parent and first/last child
If there is no border, padding, inline content, or clearance to separate the margin-top of a block with the margin-top of its first child block, or no border, padding, inline content, height, min-height, or max-height to separate the margin-bottom of a block with the margin-bottom of its last child, then those margins collapse. The collapsed margin ends up outside the parent.

That's why margins between <html> and <body> don't collapse (from the specification):

Margins of the root element's box do not collapse.

I also noticed that if <html> has default background-color and <body> has specified background-color + margin, the background color fills the entire space of <html>, for example:

html, body {
    height: 100%;
}

body {
    margin: 20px;
    background-color: yellow;
}

But if you set background-color to <html>, it works like a normal block, for example:

html, body {
    height: 100%;
}

html {
    background-color: red;
}

body {
    margin: 20px;
    background-color: yellow;
}

I summary, I recommend you to use other approaches to disable collapsing margins, or add another wrapper for <body>, for example:

body {
    margin: 0;
    padding: 0;
}

.body-wrapper {
    position: relative;
    overflow: hidden;
    background-color: silver;
}

.absolute {
    position: absolute;
    top: 0;
    left: 0;
    width: 50px;
    height: 50px;
    background-color: darkblue;
}

.wrapper {
    position: relative;
    width: 50%;
    height: 200px;
    overflow: hidden;
    background-color: yellow;
}

.wrapper > .absolute {
    background-color: darkcyan;
}
<div class="body-wrapper">
    <div class="absolute"></div>
    <h1>Some title</h1>

    <div class="wrapper">
        <div class="absolute"></div>
        <h1>Some title</h1>
    </div>
</div>
Radom answered 8/11, 2015 at 17:2 Comment(0)
S
6

This is another instance of the phenomenon known as margin-collapsing.

I'll try to explain what is happening:

When you move div.absolute with position absolute, it pulls it out of the flow of the content. This causes the first h1 to act as the first-child of its parent, which is body in this case.

The h1's margin then gets collapsed outside the body, due to margin-collapsing. That is why top: 0; is not in the upper-left of the viewport. It is in the upper left of the body.

If you need to get this to work, then add a html {position: relative; overflow: hidden:}.

Shedevil answered 8/11, 2015 at 15:52 Comment(9)
Ok, thanks for answer, but why body's background-color from top? And why padding: 1px fixes that?Kassi
I can't answer that with any certainty. All I know is margin-collapsing is stupid, in my opinion. If you want to make the color outside the body, then add html {background: white;}Shedevil
See my reply and the margin-collapsing of webkit is collapse by default. help.dottoro.com/lcptobdv.phpIlluminative
Collapsing margins is not stupid. Without it, adjacent paragraphs would have twice the spacing between then. Then, if you reduced the margin to compensate, you would have to add something to increase margin when you wanted spacing to other non-paragraphs. This would apply to all elements.Jeu
@Rob, Does collapsing margins explain why top: 0 works in .wrapper (the yellow box), but not in body? Does it explain why the gray background of body covers the entire viewport, but the top: 0-aligned box starts lower? If so, an explanation would be appreciated. Thanks.Bagley
Stupid or not the margin-collapsing isn't the problem, I get the same problem with the setting on -webkit-margin-after-collapse: collapse and -webkit-margin-before-collapse:collapse on body and htmlIlluminative
@Michael_B Ugh. I've already moved on but add a border to .wrapper and see what happens differently. Don't know if it applies cause I've changed gears.Jeu
@Jeu Ugh, Michael_B moved on before you did. I have the answer but no cares, so I'll move on as well.Illuminative
Since y'all have moved on, don't mind if I do...Palp
B
3

This is interesting behavior with little documentation to be found online (at least on a basic search).

I'll start by saying I don't know the reason why the absolutely positioned box doesn't align to the corner of body, as it would be expected to do. But here are a few observations:

  1. The expected positioning works on IE11. The gap exists in Chrome and FF.

  2. If you remove position: relative from body, the expected positioning works (as tested in Chrome, FF & IE11). demo

  3. If you apply just 1px padding to body, it also works cross-browser. demo

  4. If you add a border to body, it also works. demo


UPDATE

As a quick addendum to @BoltClock's accepted answer, this problem could have been easily illustrated and understood by just adding a background color to the root element in the original code...

html { background-color: red; }

demo

... and by removing the default margin on the h1:

h1 { margin: 0; }

demo

Bagley answered 8/11, 2015 at 14:58 Comment(5)
2. When I remove position: relative from body it means, that absolute element takes its values from browser window, because it hasn't relative parent. Therefore it looks good. Interesting behavior:) Left to learn what did padding: 1px in this situation)Kassi
The reason adding the border "fixes" it is due to collapsing margins as explained in the other answer by @QuantasticalJeu
No it isn't -webkit-margin-collapse: collapse is already set. It's background-origin see my solution.Illuminative
"If a positioned ancestor doesn't exist, then body is used." No, the initial containing block is used - and the initial containing block is at a higher level than body (above the root element, in fact). Relative positioning on body isn't redundant. IE's behavior is incorrect. I posted an answer btw.Palp
@BoltClock, I knew when I wrote body instead of initial containing block I was leaving myself open for a correction, but I said it because I suspected it was true. I guess I was wrong. I appreciate the clarification.Bagley
E
1

This is caused by h1 having a default margin, which is keeping the body apart from h1 and its sibling .absolute.

So all you have to do is reset the margin in h1, like you did for body:

snippet with every margin resetted

body {
  position: relative;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background-color: silver;
}
h1 {
  margin: 0;
 /*if you want to see full text, put this into the flow*/   
  position:relative;
  z-index:1;
}
.absolute {
  position: absolute;
  top: 0;
  left: 0;
  width: 50px;
  height: 50px;
  background-color: darkblue;
}
.wrapper {
  position: relative;
  overflow: hidden;
  width: 50%;
  height: 200px;
  overflow: hidden;
  background-color: yellow;
}
.wrapper > .absolute {
  background-color: darkcyan;
}
<div class="absolute"></div>
<h1>Some title</h1>

<div class="wrapper">
  <div class="absolute"></div>
  <h1>Some title</h1>
</div>

snippet with every no margin resetted

body {
  position: relative;
  /* margin: 0; */
  padding: 0;
  overflow: hidden;
  background-color: silver;
}
h1 {
  /* margin: 0; */
  /*if you want to see full text, put this into the flow*/
  position: relative;
  z-index: 1;
}
.absolute {
  position: absolute;
  top: 0;
  left: 0;
  width: 50px;
  height: 50px;
  background-color: darkblue;
}
.wrapper {
  position: relative;
  overflow: hidden;
  width: 50%;
  height: 200px;
  overflow: hidden;
  background-color: yellow;
}
.wrapper > .absolute {
  background-color: darkcyan;
}
<div class="absolute"></div>
<h1>Some title</h1>

<div class="wrapper">
  <div class="absolute"></div>
  <h1>Some title</h1>
</div>
Elute answered 8/11, 2015 at 15:12 Comment(2)
So why doesn't the problem exist in .wrapper?Bagley
I did not set downvote. The question about why the box doesn't align to the top left corner of body. Why code inspector show me that body begins lower, but background-color from top?Kassi

© 2022 - 2024 — McMap. All rights reserved.