51

I need to measure the offsetHeight of a div that is inside of a hidden element.

<div id="parent" style="display: none;">
    <div id="child">Lorem Ipsum dolor sit amet.</div>
</div>

The parent div must be set to "display:none". I have no control over that. I realize that the offsetHeight of the child div is going to be 0. I need to find a workaround.

Something I've toyed with is when the page loads, I copy the childnodes of parent, inject in a div on the page that is set to "visiblity:hidden". Then I measure the height of those elements, and remove the nodes when done.

Any other thoughts?

Update: What I wound up having to do was this:

Using YUI 2, on page load, I found all elements of that given classname that were either set to display:none, or whose height and width was 0 (that's one way of measuring whether an element exists, or a parent is set to display:none). I then set that element to display:block. I then checked it's parent for the same thing and showed the parents until it finds a visible parent. Once highest display:none ancestor is set to display:block, I can measure my element.

Once all elements are measured I reset all of the elements back to display:none.

Jamis Charles
  • 5,827
  • 8
  • 32
  • 42

14 Answers14

31

You need to make element's parent visible for that one very short moment while you're getting element's dimensions. In a generic solution, all ancestors are usually traversed and are made visible. Then their display values are set back to original ones.

There are performance concerns of course.

We considered this approach in Prototype.js implementation but ended up with getWidth and getHeight making only actual element visible, without traversing ancestors.

The problem with alternative solutions - such as taking element out of "hidden" parent - is that certain styles might no longer apply to an element once it's out of its "regular" hierarchy. If you have a structure like this:

<div class="foo" style="display:none;">
  <div class="bar">...</div>
</div>

and these rules:

.bar { width: 10em; }
.foo .bar { width: 15em; }

then taking element out of its parent will actually result in wrong dimensions.

kangax
  • 38,898
  • 13
  • 99
  • 135
  • 1
    I've considered this... This seems like it may be very resource intensive... What do you think? – Jamis Charles Sep 24 '09 at 19:13
  • Edited post to answer your concerns (and some thoughts on alternative approach). – kangax Sep 24 '09 at 21:28
  • 1
    I could not see any flickering while minutely showing the element. I took the approach of only showing it while getting the height, then hiding it. That is also what jQuery's slideToggle() does. It works nicely. +1 – Travis J Jan 08 '13 at 22:13
  • 3
    Why not just invoke window.getComputedStyle(elem).getPropertyValue("height"), works even when hidden! – Ashwin Prabhu May 07 '15 at 12:02
  • 4
    @Ashwin Prabhu that works perfectly! As long as css height is defined, and not auto.. Nice! – Jonathan Marzullo Aug 27 '15 at 16:38
  • @AshwinPrabhu What is the jQuery way to do `window.getComputedStyle(elem).getPropertyValue("height")`? – Vishal Kumar Sahu Sep 29 '17 at 20:39
  • Before showing the element you can temporarily add overflow:hidden and max-height: 0 to its style, then measure its scrollHeight - this way you are sure there won't be any flickering, as the measured element is never really displayed – Maciej Sawicki Dec 07 '20 at 17:13
28

If you use style.display = "none", the element will have 0 width and height,
but using the style.visibility = "hidden" instead, the element will have the width and height calculated by the browser (as normally).

T J
  • 42,762
  • 13
  • 83
  • 138
  • 1
    I think this would be the best solution. You could even set a z-index if the hidden element gets in the way. – Buttars Nov 03 '16 at 15:29
  • 1
    Some users might experience a transparent square where the div is hidden (because the div's height didn't change) after setting visibility to hidden. You can add max-height:0;overflow:hidden; and then use the element's scrollHeight – Erez Shlomo Sep 01 '20 at 10:01
26

A workaround is to set the height to 0

.hidden { 
  height: 0; 
  overflow: hidden; 
}

Then to get the elements scrollHeight.

document.querySelector('.hidden').scrollHeight

The scrollHeight will correctly give you the height though the element does not appear. I don't think it affects element flow either.

Example: https://jsfiddle.net/de3vk8p4/7/
Reference: https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements#How_big_is_the_content.3F

Kirill
  • 2,590
  • 1
  • 17
  • 16
  • It doesn’t exactly answer the question (`display: none`) but it helped me to resolve similar issue. To know the actual height you might need to also take padding into consideration. – Kirill Nov 21 '19 at 09:22
  • This will cause accessibility problems in screen readers. (As so-called 'hidden' content won't actually be hidden on screen readers.) – Eliezer Berlin Mar 21 '23 at 14:41
17

You could clone the element, absolutely position it at -10000,-10000, measure the clone and destroy it.

lod3n
  • 2,893
  • 15
  • 16
  • Yes, you would have to - ideally just after the original so any CSS is still intact. – James Sep 24 '09 at 21:06
  • Brilliant idea. Made it into a jQuery function: http://stackoverflow.com/a/12463110/373345 – dsomnus Sep 17 '12 at 16:14
  • 1
    Good idea! But if the element is already set to `display: none`, why not just make the necessary temporary changes to the element itself, measure it, and then undo the changes to move it back to the original position? – rinogo Jul 02 '15 at 22:31
  • sweet! THanks I think I could implement this. +1 :) – Gogol Mar 20 '17 at 07:46
  • @rinogo that might cause unnecessary flicker in screen as the element gets shown for a very short time. Cloning it with an absolute position is a much better solution as you are not touching the original element atall. – Gogol Mar 20 '17 at 07:48
  • I personally don't like any such approach. Sometimes things become uglier. – Vishal Kumar Sahu Sep 29 '17 at 20:40
16

Made a pure js solution with no Jquery and with no cloning (which I guess is faster)

var getHeight = function(el) {
    var el_style      = window.getComputedStyle(el),
        el_display    = el_style.display,
        el_position   = el_style.position,
        el_visibility = el_style.visibility,
        el_max_height = el_style.maxHeight.replace('px', '').replace('%', ''),

        wanted_height = 0;

    // if its not hidden we just return normal height
    if(el_display !== 'none' && el_max_height !== '0') {
        return el.offsetHeight;
    }

    // the element is hidden so:
    // making the el block so we can meassure its height but still be hidden
    el.style.position   = 'absolute';
    el.style.visibility = 'hidden';
    el.style.display    = 'block';

    wanted_height     = el.offsetHeight;

    // reverting to the original values
    el.style.display    = el_display;
    el.style.position   = el_position;
    el.style.visibility = el_visibility;

    return wanted_height;
}

here is the demo https://jsfiddle.net/xuumzf9k/1/

Please let me know if you can find any improvements to this (as I use this in my main projects)

Claudiu
  • 3,700
  • 1
  • 38
  • 35
  • 4
    You would better think about that it's ancestors https://jsfiddle.net/rantiev/xuumzf9k/2/ – Rantiev Apr 08 '15 at 17:12
  • Absolute positioning seems unnecessary and affects the height, at least when using paragraphs with default browser styles in Chrome. – Curtis Blackwell Feb 15 '17 at 17:42
  • 1
    The absolute positioning is used (when hidden) *especially not to affect any other layout*. The position is reverted before the element gets its original visibility. – Claudiu Feb 16 '17 at 19:37
  • 1
    Still a nice piece of code; I like the simplicity. Thanks for sharing! Though I use scrollHeight instead of offsetHeight, feeling that it might suit my case better. I am not sure whether this is the most intelligent thing to do. I feel that depending on the use-case even a mixture would be nice...one way or the other I would prefer to have a configurable spec-method for this. – Wolfone Oct 06 '18 at 20:30
  • Beware: this method restores style attributes from the computed style element (i.e with already applied css classes). So, if you remove a specific class from your element, it will still have the styles of the removed class. – Yan Mar 31 '19 at 16:43
5

So here's working jQuery solution based on lod3n's answer and with help of 999's comment:

var getHiddenElementHeight = function(element){
    var tempId = 'tmp-'+Math.floor(Math.random()*99999);//generating unique id just in case
    $(element).clone()
    .css('position','absolute')
    .css('height','auto').css('width','1000px')
    //inject right into parent element so all the css applies (yes, i know, except the :first-child and other pseudo stuff..
    .appendTo($(element).parent())
    .css('left','-10000em')
    .addClass(tempId).show()
    h = $('.'+tempId).height()
    $('.'+tempId).remove()
    return h;
}

Enjoy!

dsomnus
  • 1,391
  • 14
  • 21
5

A helper function ---

function getElementHeight(el) {
  var clone           = el.cloneNode(true);
  var width           = el.getBoundingClientRect().width;
  clone.style.cssText = 'position: fixed; top: 0; left: 0; overflow: auto; visibility: hidden; pointer-events: none; height: unset; max-height: unset; width: ' + width + 'px';
  document.body.append(clone);
  var height = clone.getBoundingClientRect().height + 'px';
  clone.remove();
  return height;
}

Creates a clone, appends it to the DOM (hidden), takes the height, then removes it.

Position of fixed and the top/left are in case your app allows scrolling at the body-level - it attempts to prevent a scrollbar rave party - can remove if you handle scrolling in children elements.

Overflow, height, and max-height settings to attempt to 'reset' height settings and let it be it's natural height on the clone.

Visibility for the obvious and pointer-events as a 'just in case' the rendering of the element takes a while and don't want to interrupt user-input.

An example having an 'accordion-like' animated open/close allowing for dynamic heights.

function getElementHeight(el) {
  var clone = el.cloneNode(true);
  clone.style.cssText = 'position: fixed; top: 0; left: 0; overflow: auto; visibility: hidden; pointer-events: none; height: unset; max-height: unset';
  document.body.append(clone);
  var height = clone.getBoundingClientRect().height + 'px';
  clone.remove();
  return height;
}

var expanded = false;
var timeout;

function toggle() {
  var el = document.getElementById('example');
  expanded = !expanded;

  if (expanded) {
    el.style.maxHeight = getElementHeight(el);

    // Remove max-height setting to allow dynamic height after it's shown
    clearTimeout(timeout);
    var openTimeout = timeout = setTimeout(function() {
      el.style.maxHeight = 'unset';
      clearTimeout(openTimeout);
    }, 1000); // Match transition
  } else {
    // Set max height to current height for something to animate from
    el.style.maxHeight = getElementHeight(el);

    // Let DOM element update max-height, then set to 0 for animated close
    clearTimeout(timeout);
    var closeTimeout = timeout = setTimeout(function() {
      el.style.maxHeight = 0;
      clearTimeout(closeTimeout);
    }, 1);
  }
}
#example {
  overflow: hidden;
  max-height: 0;
  transition: max-height 1s;
}
<button onclick="toggle()">Toggle</button>
<div id="example">
  <textarea>Resize me</textarea>
</div>
Charly
  • 881
  • 10
  • 19
3

In the JS please use 'scrollHeight'

Example Code

Assume that this div is hidden in DOM

<div class="test-div">
 //Some contents 
<div>

Javascript to find this div height

const testHeight = document.querySelector('.test-div');

testHeight.scrollHeight
prasanth pr
  • 171
  • 1
  • 4
1

Use z-index to hide element under non-transparent element, show it, and get height.

Anatoliy
  • 29,485
  • 5
  • 46
  • 45
1

Until the element is rendered, it has no height. Even if you clone the parent object and display it somewhere that can't be seen by the user, there's not guarantee that the clone will have the same height as the final size of the hidden object.

There are many things that can affect the height that wouldn't necessarily be rendered in the clone - anything in the DOM and its interaction with the CSS rules could cause a change in rendering any other element of the DOM. Short of cloning the entire document (and even that's not fool-proof) you have no way of determining the height of the hidden object.

If you must know the height before it's displayed to the user, you'll have to "hack" it by displaying it for as short of a time as possible then hiding it again. Most likely, the user will see this hiccup and not be pleased by the result.

Brandon Belvin
  • 445
  • 7
  • 14
  • The "hiccup" could be avoided by moving it off the page using negative positioning and using visiblity:hidden possibly – Jamis Charles Sep 24 '09 at 19:31
  • 2
    But if you move it out of its true position on the page, you cannot _guarantee_ that the height will be correct. The only way you can be 100% sure it's right is to display it in-place. Moving it elsewhere might give you the correct answer, but then you have to accept the possibility of a rendering difference. – Brandon Belvin Sep 24 '09 at 19:44
  • That's why element shouldn't be moved; rather - hidden with "visibility:hidden". – kangax Sep 25 '09 at 02:12
  • 1
    But putting it in `visibility:hidden` can cause offset of other elements by adding the element back into the DOM. You still risk getting the hiccup, even though the element causing the hiccup will remain hidden. – Brandon Belvin Sep 25 '09 at 03:29
  • @Brandon how will it cause hiccups if "visibility:hidden" merely affects **visibility** of an element and not its layout? – kangax Sep 25 '09 at 03:47
  • 1
    Because the browser will carve out the room to display the item when it's no longer `display:none`. `visibility:hidden` tells the browser to not display the item, but it will set aside the room to display it. Elements that share the same z-order as the element that formerly was marked `display:none` have the potential of being moved, resized, etc because of this element "re-appearing" into the rendered DOM. – Brandon Belvin Sep 25 '09 at 12:49
  • I see now. I missed the fact that you were talking about **other elements** hiccups :) – kangax Sep 25 '09 at 16:01
  • I tried the clone approach and that failed because the width no longer matches up, which messes up the height. – Jamis Charles Sep 28 '09 at 18:27
  • Which is exactly what I noted. You cannot necessarily get the proper height without rendering it in-place. I believe my original post answers your question completely and correctly. – Brandon Belvin Sep 28 '09 at 20:57
1

So, you cannot even change the display:none; to height:0; overflow:hidden; ? Maybe you could override that in your own stylesheet like so:

div#parent { display: block !important; height:0; overflow:hidden; }

And then as you are using YUI (assuming YUI 2) you could use this:

var region = YAHOO.util.Dom.getRegion('child');

To get the dimensions and offset of the child.

Max
  • 15,693
  • 14
  • 81
  • 131
1

Try to use:

#parent{ display:block !important; visibility:hidden; position:absolute} 
Peter O.
  • 32,158
  • 14
  • 82
  • 96
emelendez
  • 21
  • 1
0

What I wound up having to do was this:

Using YUI 2, on page load, I found all elements of that given classname that were either set to display:none, or whose height and width was 0 (that's one way of measuring whether an element exists, or a parent is set to display:none). I then set that element to display:block. I then checked it's parent for the same thing and showed the parents until it finds a visible parent. Once highest display:none ancestor is set to display:block, I can measure my element.

Once all elements are measured I reset all of the elements back to display:none.

Jamis Charles
  • 5,827
  • 8
  • 32
  • 42
-8

Did you try this ?

setTimeout('alert($(".Var").height());',200); 
Paolo_Mulder
  • 1,233
  • 1
  • 16
  • 28