I want to calculate the total "height" of a div element considering the effect of collapsed margins because of child elements, that is, the total space that div element occupies in the document. I'm having a hard time thinking of the best algorithm / approach to do this.
Consider, for example, a div element with a margin-top
of 10px
and a height
of 50px
. This div element has a child <h2>
element that has a margin-top
of 20px
. The div
's margin
will then collapse and the actual "height"
of that div
will be 70px
. However, using jQuery methods, we are only able to get the height
of the div
without considering it's margins
, or considering it's 10
pixel margin
which would give us the wrong value:
$(elem).outerHeight() // 50
$(elem).outerHeight(true) // 60
To help illustrate my point, here is a jsfiddle I created with two examples.
My best guess at the moment is we have to iterate over all children of the div in some way and calculate the highest top and bottom margin.
According to what I understand from the W3C specification, we can skip this iteration for the top margin
if the target div has a top-border-width
or a top-padding
. Ditto for the bottom margin
.
Any help would be greatly appreciated. Thanks!
EDIT:
One (ugly) solution I thought about was wrapping the target div element in another div. Then, we quickly add and remove a transparent borderTop and borderBottom to the wrapping div, measuring it's height in between. The borders will force the wrapping div's margin not to collapse with its children's margins. Something like this:
var collapsedHeight = function( target ) {
var $wrapper = $('<div />'),
$target = $(target);
$wrapper.insertAfter($target);
$target.appendTo($wrapper);
$wrapper.css({
borderTop: '1px solid transparent',
borderBottom: '1px solid transparent'
});
var result = $wrapper.outerHeight() - 2;
$target.insertAfter($wrapper);
$wrapper.remove();
return result;
};
I made a jsFiddle for it here.
I want to calculate the total "height" of a div element considering the effect of collapsed margins because of child elements, that is, the total space that div element occupies in the document. I'm having a hard time thinking of the best algorithm / approach to do this.
Consider, for example, a div element with a margin-top
of 10px
and a height
of 50px
. This div element has a child <h2>
element that has a margin-top
of 20px
. The div
's margin
will then collapse and the actual "height"
of that div
will be 70px
. However, using jQuery methods, we are only able to get the height
of the div
without considering it's margins
, or considering it's 10
pixel margin
which would give us the wrong value:
$(elem).outerHeight() // 50
$(elem).outerHeight(true) // 60
To help illustrate my point, here is a jsfiddle I created with two examples.
My best guess at the moment is we have to iterate over all children of the div in some way and calculate the highest top and bottom margin.
According to what I understand from the W3C specification, we can skip this iteration for the top margin
if the target div has a top-border-width
or a top-padding
. Ditto for the bottom margin
.
Any help would be greatly appreciated. Thanks!
EDIT:
One (ugly) solution I thought about was wrapping the target div element in another div. Then, we quickly add and remove a transparent borderTop and borderBottom to the wrapping div, measuring it's height in between. The borders will force the wrapping div's margin not to collapse with its children's margins. Something like this:
var collapsedHeight = function( target ) {
var $wrapper = $('<div />'),
$target = $(target);
$wrapper.insertAfter($target);
$target.appendTo($wrapper);
$wrapper.css({
borderTop: '1px solid transparent',
borderBottom: '1px solid transparent'
});
var result = $wrapper.outerHeight() - 2;
$target.insertAfter($wrapper);
$wrapper.remove();
return result;
};
I made a jsFiddle for it here.
Share Improve this question edited Oct 27, 2011 at 15:24 vitorbal asked Oct 26, 2011 at 23:05 vitorbalvitorbal 3,05126 silver badges24 bronze badges 2- 1 Have you considered avoiding the scenarios where the margin of an ancestor overrides the margin of the element in question? It seems like bad design to me, I wouldn't want that to occur on my pages... – Šime Vidas Commented Oct 27, 2011 at 14:05
- Hi Šime Vidas, I pletely agree, However I guess my question was more when considering we have no control over the page design. When this code would be in a bookmarklet, for example. – vitorbal Commented Oct 27, 2011 at 15:07
3 Answers
Reset to default 5Consider this hack:
$( elem ).wrap( '<div style="border:1px solid transparent;"></div>' ).parent().height()
The above expression returns 70
which is what you want, right?
So, the idea is to wrap your element in a DIV that has a transparent border set. This border will prevent the margins of your element to interfere with the margins of its previous and next sibling.
Once you get the height value, you can unwrap your element...
For a solution that doesn't involve DOM manipulation, you can achieve the same effect by adding padding to the element being measured and then removing it afterwards.
function getRealHeight(elementP) {
var
element = (elementP instanceof jQuery)? elementP : $(element),
padTop = parseInt(element.css('paddingTop')),
padBottom = parseInt(element.css('paddingBottom')),
offset,
height;
if (padTop == 0 || padBottom == 0) {
offset = 0;
if (padTop == 0) {
element.css('paddingTop', 1);
offset += 1;
}
if (padBottom == 0) {
element.css('paddingBottom', 1);
offset += 1;
}
height = (element.outerHeight(true) - offset);
if (padTop == 0) {
element.css('paddingTop', '');
}
if (padBottom == 0) {
element.css('paddingBottom', '');
}
} else {
height = element.outerHeight(true);
}
return height;
}
The bonus of this solution; you can sidestep the overhead of wrap/unwrap.
You can make it a jQuery plugin:
(function ($) {
$.fn.extend({
//plugin name - realHeight
realHeight: function (options) {
//Settings list and the default values
var defaults = {};
var options = $.extend(defaults, options);
function getRealHeight(elementP) {
var
element = (elementP instanceof jQuery) ? elementP : $(element),
padTop = parseInt(element.css('paddingTop')),
padBottom = parseInt(element.css('paddingBottom')),
offset,
height;
if (padTop == 0 || padBottom == 0) {
offset = 0;
if (padTop == 0) {
element.css('paddingTop', 1);
offset += 1;
}
if (padBottom == 0) {
element.css('paddingBottom', 1);
offset += 1;
}
height = (element.outerHeight(true) - offset);
if (padTop == 0) {
element.css('paddingTop', '');
}
if (padBottom == 0) {
element.css('paddingBottom', '');
}
} else {
height = element.outerHeight(true);
}
return height;
}
return getRealHeight($(this));
}
});
})(jQuery);