8

I'm trying to figure out, why setting overflow-x: hidden to body of an HTML page makes my element position: fixed even if I set it to position: absolute.

The effect is better understood in this demo.

Here is the code:

html,
body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  overflow-x: hidden;/* If I remove this line everything is how I expect it to be! */
}

div.page {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 100%;
  border: 1px solid red;
  margin-bottom: 200px;
}

div.background {
  background: blue;
  position: absolute;
  width: 100%;
  height: 10%;
}
<div class='background'></div>
<div class='page'></div>
<div class='page'></div>
<div class='page'></div>
<div class='page'></div>

Question

What's the relationship between overflow-x: hidden and positioning? Why does setting that property cause my element to become position: fixed instead of position: absolute?

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
drinchev
  • 19,201
  • 4
  • 67
  • 93

2 Answers2

28

The element is still position: absolute, but it appears fixed thanks to some rather complicated interactions between position, overflow and the box model. Incredibly, none of these behaviors is unspecified or a bug in any browser — it's actually completely by design, if a little counter-intuitive.

It basically boils down to the following:

  • An absolutely positioned element is anchored to the initial containing block unless any of its ancestors is positioned. (This is why adding position: relative to body works as suggested in another answer.)

  • You have width: 100%; height: 100%; on both html and body; this prevents the initial containing block from ever expanding beyond the viewport (the visible area), so the initial containing block never scrolls.

  • Since the initial containing block doesn't scroll, neither does the absolutely positioned element. This causes it to appear fixed even though the rest of the page does scroll.

Oddly enough, this works on IE6 too.


Longer explanation:

  • An absolutely positioned element is anchored to the initial containing block unless any of its ancestors is positioned.

    The spec on the overflow property, which incidentally contains another example of the same issue you're observing where an element with overflow: scroll is interacting with an absolutely positioned descendant element, states the following:

    This property specifies whether content of a block container element is clipped when it overflows the element's box. It affects the clipping of all of the element's content except any descendant elements (and their respective content and descendants) whose containing block is the viewport or an ancestor of the element.

    Your absolutely positioned element is a descendant whose containing block is the initial containing block (also the containing block of the html element), because both html and body are not positioned. This is according to another section of the spec. This prevents overflow clipping on html and body from having any effect on your absolutely positioned element since it's anchored to the initial containing block.

  • You have width: 100%; height: 100%; on both html and body; this prevents the initial containing block from ever expanding beyond the viewport (the visible area), so the initial containing block never scrolls.

    The spec then states the following, further down the same section:

    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'. The 'visible' value when used for the viewport must be interpreted as 'auto'. The element from which the value is propagated must have a used value for 'overflow' of 'visible'.

    To put it more simply:

    • If html is not overflow: visible, apply that to the viewport instead, and turn html to overflow: visible. The overflow value given to body is not affected.

    • If html is overflow: visible, but body is not, apply that to the viewport instead, and turn body to overflow: visible.

    (Setting overflow-x or overflow-y to anything other than visible for an element causes the shorthand overflow to no longer be equal to visible for that element.)

    Normally, this means the viewport should scroll naturally along with html and body, since only one scrollbar should exist at a time.

    However... you also give both html and body a width and height of 100%! That means 100% those of its container. The container of body is html, and the container of html is the initial containing block. But since you can't actually use CSS to control the size of the viewport — that's entirely handled by the browser — you're left with the two elements being constrained to 100% the height of the viewport (also known as the fold). The viewport itself does not have to expand beyond the fold since none of its contents require more space than is visible (note that absolutely positioned elements are never taken into account). The viewport therefore does not generate a scrollbar (and neither does html); the scrollbar that you see belongs to body.

    If you had not set the width or height properties, then they would have defaulted to auto, causing both html and body to expand with their contents and always be the same size as the entire area covered by the initial containing block, including the area below the fold. This prevents body from ever generating a scrollbar because it will always stretch to fit its contents, so you only see the viewport scrollbar, and the absolutely positioned element will scroll with the rest of the page.

  • Since the initial containing block doesn't scroll, neither does the absolutely positioned element. This causes it to appear fixed even though the rest of the page does scroll.

    What happens when you scroll, then, is that you're really scrolling the body element. Since the absolutely positioned element is anchored to the initial containing block, which never scrolls, it appears to be fixed instead of scrolling.

    By the way, this is also why the element appears to overlap the scrollbar when it doesn't scroll at all. The scrollbar belongs to body, which is beneath the absolutely positioned element. If you remove the overflow-x declaration or the width and height declarations from html and body, the scrollbar that you see belongs to the viewport instead. However, if you position body, the scrollbar still belongs to body, but the element also becomes a child of body so it won't overlap the scrollbar.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
4

You need to add position: relative to body. Your div.background has position: absolute on it, which is causing it to stick to the top of the HTML document. If you don't want that, you need to give its parent (in this case, the body tag) position: relative.

DEMO #1: http://tinkerbin.com/nmI74RpC

Alternatively, you can remove position: absolute from div.background, but I'm not sure how that would affect the rest of your layout.

DEMO #2: http://tinkerbin.com/PP5PpbuC

zxqx
  • 5,115
  • 2
  • 20
  • 28
  • 1
    Yes, great... but can you explain why is this happening? – drinchev Feb 05 '13 at 22:54
  • It's because you have `position: absolute` on `div.background`, which is positioning the background div absolutely to the window, causing it to stick to the top. If you want to position it relative to its parent (in this case, body), then you need to give `position: relative` to the body tag. Here's a demo without `position: absolute` on `div.background`: http://tinkerbin.com/PP5PpbuC – zxqx Feb 05 '13 at 23:06
  • Updated answer for clarity. – zxqx Feb 05 '13 at 23:16
  • so, what's the case with `overflow-x: hidden`. I don't think you are right here, because when I have my first child positioned absolute it is always on the top of the page, not the window... e.g. I can scroll the page and the element positioned absolute will hide http://tinkerbin.com/zgr7YOjf – drinchev Feb 05 '13 at 23:17
  • 1
    While this answer is right in that `position: absolute` causes an element to stick to the top of the page, the element should still scroll (only `position: fixed` will make it stick even when you scroll). In this case, something else is causing it *not* to scroll; I've posted an answer detailing that. – BoltClock Feb 06 '13 at 23:55