6

This isn't a bug because the behaviour is consistent across FF, Chrome, IE9 and Safari on Win7.

The app i'm working on is 3rd party to the host page, so the CSS is immutable.The script attempts to align a new div with an existing element.

  • The body is position:relative
  • There is an H1 at the top of the page
  • The margin from the H1 seems to change where the body 0,0 is calculated from - even though the background on the body extends all the way to the edge, and it's offsetTop property reports 0
  • Setting a border on the body solves the problem - seems strange behaviour but is consistent across browsers? (not a viable solution)
  • Removing the H1 margins solves the problem (not a viable solution)

Example here, the JS is commented for replicating each case:
http://codepen.io/anon/pen/EGvlb

I don't think this is a bug with jQuery - it seems to be down to a legitimate relationship between the H1 margin and the body element?

$(function(){

  /* Setting body to position:relative breaks offset()
     because H1 margin moves body down */
  $(document.body).css({position: "relative"});

  /* Strange: putting a border on body fixes things? */
  //$(document.body).css({border: "1px solid #000"});

  /* Removing H1 margin removes problem */
  //$("h1").css({margin: 0});

  $("#overlay").css({
      left: $("#existing").offset().left,
      top: $("#existing").offset().top
  })
});
robC
  • 2,592
  • 1
  • 23
  • 28
  • Starring this because I accidentally stayed up too long figuring this one out. Thanks. I really ought to hit the sack now :) – BoltClock Nov 14 '12 at 22:27

2 Answers2

4

I don't think this is a bug with jQuery - it seems to be down to a legitimate relationship between the H1 margin and the body element?

Yeah: the margins of the body and the h1 are collapsing, so not only does the h1 get shifted down by its default margin, but so does body. This is why you were able to make these observations:

  • Setting a border on the body solves the problem - seems strange behaviour but is consistent across browsers? (not a viable solution)
  • Removing the H1 margins solves the problem (not a viable solution)

Anyway, by setting the body to position: relative, you're telling #overlay to absolutely position itself with body's top offset as the origin. Your script then moves it relative to the offset of #existing. The value of its offsetTop is relative to the viewport, which is calculated based on the position of this box... which itself is relative to h1 because it directly follows the h1 in normal flow.

Because the margins of the h1 and body elements are collapsing, they have the same rendered margins. What happens then is twofold:

  1. #existing is pushed down by the h1 as its following sibling in normal flow. This increases its offset from the viewport (in your CodePen test case, it's the top of the preview pane), resulting in its offsetTop being greater.

  2. When the body is set to position: relative, #overlay ends up getting positioned with its origin set to that of the body, which itself has also been pushed down due to collapsing with the h1's margin. This increases the top offset of its origin, which would not happen if you had not positioned the body, because then #overlay uses the origin of the viewport instead, which never moves.

The distance of #existing from the top of the viewport is added on to the offset of #overlay from its origin, that being the body, resulting in this effect.

In summary: due to margin collapse, the browser ends up having to account for downward shifts in both h1 and body. The h1 influences #existing and in turn its offsetTop, while the body influences #overlay when you position it relatively. So the shifting effect appears twofold.

As a quick workaround, you can subtract the h1 margins from the offsets of #existing for the purposes of offsetting #overlay:

  $("#overlay").css({
      left: $("#existing").offset().left - $("h1").offset().left,
      top: $("#existing").offset().top - $("h1").offset().top
  })

Updated example

Notice that using $(document.body) instead of $("h1") above won't work. This is because margin collapse is purely a rendering effect, and does not actually influence the body's offsets until you give it its own margin (in other words, the body margins are still computing to zero, so the DOM remains none the wiser).


Not directly related to the issue at hand, but worth addressing anyway:

  • The margin from the H1 seems to change where the body 0,0 is calculated from - even though the background on the body extends all the way to the edge, and it's offsetTop property reports 0

The first and last parts of this point have been addressed above.

As for the body background: while it appears to extend all the way to the edge, this extended background does not actually belong to body - it is propagated from body to, and thus belongs to, the root element (html) and the viewport. See this answer for an explanation.

Oh and this:

This isn't a bug because the behaviour is consistent across FF, Chrome, IE9 and Safari on Win7.

Made me smile. Because for once, somebody is right in saying it.

Community
  • 1
  • 1
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • Great answer thanks! I have come up with a more generic solution, a 'zero-point' div at 0,0: http://codepen.io/anon/pen/IdhoK. I still don't understand though why adding a border to the body removes it's margin? – robC Nov 15 '12 at 11:06
  • @robC: Because from the spec, it says that margins are only eligible for collapsing if no padding, border or clearance separates them. By adding a border, you prevent the margins from collapsing, so h1's margin doesn't get applied to body. – BoltClock Nov 15 '12 at 15:49
  • Wow that spec is some esoteric reading, I can't help feeling this type of feature is a lot less useful than it is confusing. Thankyou for explaining :) – robC Nov 15 '12 at 16:11
  • Ok, but suppose you do not have control over the site, how do you get the exact position of any child element, when document.body is offset in this way. offsetTop is going to be wrong. – jayarjo Mar 30 '15 at 07:55
  • I think you'd need to take the margins into account when calculating. You'll have to find out these values in advance. – BoltClock Mar 30 '15 at 07:56
  • But again, since it's not the site that you made and it can have an arbitrary structure, you by definition cannot know which child element offsets the body :/ – jayarjo Mar 30 '15 at 07:58
  • @jayarjo: If the layout is unpredictable you'll have to find another way. Post a question and see if someone can think of a solution. – BoltClock Mar 30 '15 at 08:11
0

If you change position of overlay from absolute to fixed then it working fine. that means offset value is correct. I think it is something related to position of overlay element relative to its previous element.

Anoop
  • 23,044
  • 10
  • 62
  • 76