114

It seems like in jQuery when an element is not visible width() returns 0. Makes sense, but I need to get the width of a table in order to set the width of the parent before I show the parent.

As noted below, there is text in the parent, that makes the parent skew and look nasty. I want the parent to be only as wide as the table and have the text wrap.

<div id="parent">
    Text here ... Can get very long and skew the parent
    <table> ... </table>
    Text here too ... which is why I want to shrink the parent based on the table
</div>

CSS:

#parent
{
    display: none;
}

Javascript:

var tableWidth = $('#parent').children('table').outerWidth();
if (tableWidth > $('#parent').width())
{
    $('#parent').width(tableWidth);
}

tableWidth always returns 0 since it is not visible (is my guess since it gives me a number when visible). Is there a way to get the width of the table without making the parent visible?

CodingYoshi
  • 25,467
  • 4
  • 62
  • 64
Martin
  • 11,031
  • 8
  • 50
  • 77

12 Answers12

100

Here is a trick I have used. It involves adding some CSS properties to make jQuery think the element is visible, but in fact it is still hidden.

var $table = $("#parent").children("table");
$table.css({ position: "absolute", visibility: "hidden", display: "block" });
var tableWidth = $table.outerWidth();
$table.css({ position: "", visibility: "", display: "" });

It is kind of a hack, but it seems to work fine for me.

UPDATE

I have since written a blog post that covers this topic. The method used above has the potential to be problematic since you are resetting the CSS properties to empty values. What if they had values previously? The updated solution uses the swap() method that was found in the jQuery source code.

Code from referenced blog post:

//Optional parameter includeMargin is used when calculating outer dimensions  
(function ($) {
$.fn.getHiddenDimensions = function (includeMargin) {
    var $item = this,
    props = { position: 'absolute', visibility: 'hidden', display: 'block' },
    dim = { width: 0, height: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 },
    $hiddenParents = $item.parents().andSelf().not(':visible'),
    includeMargin = (includeMargin == null) ? false : includeMargin;

    var oldProps = [];
    $hiddenParents.each(function () {
        var old = {};

        for (var name in props) {
            old[name] = this.style[name];
            this.style[name] = props[name];
        }

        oldProps.push(old);
    });

    dim.width = $item.width();
    dim.outerWidth = $item.outerWidth(includeMargin);
    dim.innerWidth = $item.innerWidth();
    dim.height = $item.height();
    dim.innerHeight = $item.innerHeight();
    dim.outerHeight = $item.outerHeight(includeMargin);

    $hiddenParents.each(function (i) {
        var old = oldProps[i];
        for (var name in props) {
            this.style[name] = old[name];
        }
    });

    return dim;
}
}(jQuery));
Tim Banks
  • 7,099
  • 5
  • 31
  • 28
  • 1
    Won't this make the browser redraw the page twice? If so, this is a suboptimal solution. – marcusklaas Jun 25 '11 at 21:18
  • @Tim Banks very nice! I actually wrote a similar extension. What i've been noticing is very strange behavior in **Firefox**, width() returns 0, for your and my plugin both. And on top of that it never factors in padding. http://jsfiddle.net/67cgB/. I'm having the hardest time figuring out what else to could do to fix it. – Mark Pieszak - Trilon.io Feb 14 '13 at 16:32
  • How can I use this hack inside jquery accordion to get hidden accordion dimensions?Need a your help. – Deepak Ingole Jul 30 '13 at 09:37
  • jQuery doesn't think its visible -> It IS visible when you retrieve the values – Mick Feb 21 '17 at 21:07
  • @Mick The element is not visible to the user. Positioning an element absolutely with visible hidden will not cause the element to be shown. – Tim Banks Feb 23 '17 at 19:38
  • Yes of course. Just your typing is not right you say u let jQuery think it is visible. In fact it has to be visible to get dimensions and it is. This would confuse newbies because you cant trick jQuery this is a JS default behavior. – Mick Feb 25 '17 at 12:06
  • The link is being blocked by the antivirus and google. Does anyone have the referenced code? – FercoCQ Mar 20 '17 at 15:00
  • 1
    @FercoCQ I added the code from the referenced blog post. – Tim Banks Mar 20 '17 at 15:28
  • 2
    [.andSelf() is deprecated in jQuery 1.8+, and removed in jQuery 3+](https://api.jquery.com/andself/). If you're using jQuery 1.8+, replace .andSelf() with [.addBack()](https://api.jquery.com/addBack/). – Tim Aug 08 '17 at 08:55
43
function realWidth(obj){
    var clone = obj.clone();
    clone.css("visibility","hidden");
    $('body').append(clone);
    var width = clone.outerWidth();
    clone.remove();
    return width;
}
realWidth($("#parent").find("table:first"));
Fábio Paiva
  • 569
  • 4
  • 7
  • dirty nice trick !!! Make sure that the clone has the right css properties (ex. if the font size of the original element is 15 you should use clone.css("visibility","hidden").css("font-size","15px"); ) – alex Dec 19 '12 at 22:27
  • 18
    Just to point out, this won't work if your element has inherited its width from any parent. It will just inherit the body width which is incorrect. – Dean Mar 05 '13 at 11:32
  • 3
    I modified `clone.css("visibility","hidden");` to `clone.css({"visibility":"hidden", "display":"table"});` which resolves the issue pointed out by @Dean – isapir Apr 30 '15 at 15:17
  • Great, thanks! It worked with little changes for my case: I've changed it to support an object to clone and a selector inside it to get the real width of it. Not all times we want to measure the same root object that is hidden. – falsarella Oct 15 '15 at 21:54
  • I used to use the same approach and I got bunch of downvotes that day. Weird that You have so many upvotes :D I'll tell You why this is bad solution and what was pointed under my answer few years ago. Once You copy element and put it directly into body, most certainly You're removing all CSS related to this specific element. For example. from this: `#some wrapper .hidden_element{/*Some CSS*/}` You're making this: `body .hidden_element`. The `#some_wrapper .hidden_element` is not used any more! As a result, Your calculated size may differ from it's original size. TLTR:You're just loosing styles – instead Feb 17 '19 at 07:27
  • I append the clone to the parent of the cloned object instead of the body. That way it inherits the same CSS styles and has the same width. – user984003 Mar 31 '23 at 14:40
8

Based on Roberts answer, here is my function. This works for me if the element or its parent have been faded out via jQuery, can either get inner or outer dimensions and also returns the offset values.

/edit1: rewrote the function. it's now smaller and can be called directly on the object

/edit2: the function will now insert the clone just after the original element instead of the body, making it possible for the clone to maintain inherited dimensions.

$.fn.getRealDimensions = function (outer) {
    var $this = $(this);
    if ($this.length == 0) {
        return false;
    }
    var $clone = $this.clone()
        .show()
        .css('visibility','hidden')
        .insertAfter($this);        
    var result = {
        width:      (outer) ? $clone.outerWidth() : $clone.innerWidth(), 
        height:     (outer) ? $clone.outerHeight() : $clone.innerHeight(), 
        offsetTop:  $clone.offset().top, 
        offsetLeft: $clone.offset().left
    };
    $clone.remove();
    return result;
}

var dimensions = $('.hidden').getRealDimensions();
Marco Kerwitz
  • 5,294
  • 2
  • 18
  • 17
  • YUI version for those who need it: https://gist.github.com/samueljseay/13c2e69508563b2f0458 – Pure Function Jun 04 '14 at 00:02
  • Is offsetTop correct for the item given that it is the clone and not the 'real' item? Should you use $this.offset().top? Also, curious what you are using the top/left for? I'm only using 'width' but wondering if I should be concerned those offsets for some reason. – Terry Mar 28 '16 at 13:50
3

Thank you for posting the realWidth function above, it really helped me. Based on "realWidth" function above, I wrote, a CSS reset, (reason described below).

function getUnvisibleDimensions(obj) {
    if ($(obj).length == 0) {
        return false;
    }

    var clone = obj.clone();
    clone.css({
        visibility:'hidden',
        width : '',
        height: '',
        maxWidth : '',
        maxHeight: ''
    });
    $('body').append(clone);
    var width = clone.outerWidth(),
        height = clone.outerHeight();
    clone.remove();
    return {w:width, h:height};
}

"realWidth" gets the width of an existing tag. I tested this with some image tags. The problem was, when the image has given CSS dimension per width (or max-width), you will never get the real dimension of that image. Perhaps, the img has "max-width: 100%", the "realWidth" function clone it and append it to the body. If the original size of the image is bigger than the body, then you get the size of the body and not the real size of that image.

Robert
  • 664
  • 9
  • 25
3

I try to find working function for hidden element but I realize that CSS is much complex than everyone think. There are a lot of new layout techniques in CSS3 that might not work for all previous answers like flexible box, grid, column or even element inside complex parent element.

flexibox example enter image description here

I think the only sustainable & simple solution is real-time rendering. At that time, browser should give you that correct element size.

Sadly, JavaScript does not provide any direct event to notify when element is showed or hidden. However, I create some function based on DOM Attribute Modified API that will execute callback function when visibility of element is changed.

$('[selector]').onVisibleChanged(function(e, isVisible)
{
    var realWidth = $('[selector]').width();
    var realHeight = $('[selector]').height();

    // render or adjust something
});

For more information, Please visit at my project GitHub.

https://github.com/Soul-Master/visible.event.js

demo: http://jsbin.com/ETiGIre/7

user229044
  • 232,980
  • 40
  • 330
  • 338
3

If you need the width of something that's hidden and you can't un-hide it for whatever reason, you can clone it, change the CSS so it displays off the page, make it invisible, and then measure it. The user will be none the wiser if it's hidden and deleted afterwards.

Some of the other answers here just make the visibility hidden which works, but it will take up a blank spot on your page for a fraction of a second.

Example:

$itemClone = $('.hidden-item').clone().css({
        'visibility': 'hidden',
        'position': 'absolute',
        'z-index': '-99999',
        'left': '99999999px',
        'top': '0px'
    }).appendTo('body');
var width = $itemClone.width();
$itemClone.remove();
rannmann
  • 308
  • 2
  • 8
  • 2
    It won't work if the element dimensions are influenced by a parent container or a more complex CSS selector, based on the layout hierarchy. – Sebastian Nowak Feb 20 '17 at 08:39
2

Before take the width make the parent display show ,then take the width and finally make the parent display hide. Just like following

$('#parent').show();
var tableWidth = $('#parent').children('table').outerWidth();
 $('#parent').hide();
if (tableWidth > $('#parent').width())
{
    $('#parent').width() = tableWidth;
}
sourcecode
  • 4,116
  • 1
  • 20
  • 14
2

One solution, though it won't work in all situations, is to hide the element by setting the opacity to 0. A completely transparent element will have width.

The draw back is that the element will still take up space, but that won't be an issue in all cases.

For example:

$(img).css("opacity", 0)  //element cannot be seen
width = $(img).width()    //but has width
Jake
  • 625
  • 6
  • 16
1

The biggest issue being missed by most solutions here is that an element's width is often changed by CSS based on where it is scoped in html.

If I was to determine offsetWidth of an element by appending a clone of it to body when it has styles that only apply in its current scope I would get the wrong width.

for example:

//css

.module-container .my-elem{ border: 60px solid black; }

now when I try to determine my-elem's width in context of body it will be out by 120px. You could clone the module container instead, but your JS shouldn't have to know these details.

I haven't tested it but essentially Soul_Master's solution appears to be the only one that could work properly. But unfortunately looking at the implementation it will likely be costly to use and bad for performance (as most of the solutions presented here are as well.)

If at all possible then use visibility: hidden instead. This will still render the element, allowing you to calculate width without all the fuss.

Pure Function
  • 2,129
  • 1
  • 22
  • 31
0

In addition to the answer posted by Tim Banks, which followed to solving my issue I had to edit one simple thing.

Someone else might have the same issue; I'm working with a Bootstrap dropdown in a dropdown. But the text can be wider as the current content at this point (and there aren't many good ways to resolve that through css).

I used:

$table.css({ position: "absolute", visibility: "hidden", display: "table" });

instead, which sets the container to a table which always scales in width if the contents are wider.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Mathijs Segers
  • 6,168
  • 9
  • 51
  • 75
0
jQuery(".megamenu li.level0 .dropdown-container .sub-column ul .level1").on("mouseover", function () {
    var sum = 0;
    jQuery(this).find('ul li.level2').each(function(){
    sum -= jQuery(this).height();
    jQuery(this).parent('ul').css('margin-top', sum / 1.8);
    console.log(sum);
    });
});

On hover we can calculate the list item height.

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
0

As has been said before, the clone and attach elsewhere method does not guarantee the same results as styling may be different.

Below is my approach. It travels up the parents looking for the parent responsible for the hiding, then temporarily unhides it to calculate the required width, height, etc.

    var width = parseInt($image.width(), 10);
    var height = parseInt($image.height(), 10);

    if (width === 0) {

        if ($image.css("display") === "none") {

            $image.css("display", "block");
            width = parseInt($image.width(), 10);
            height = parseInt($image.height(), 10);
            $image.css("display", "none");
        }
        else {

            $image.parents().each(function () {

                var $parent = $(this);
                if ($parent.css("display") === "none") {

                    $parent.css("display", "block");
                    width = parseInt($image.width(), 10);
                    height = parseInt($image.height(), 10);
                    $parent.css("display", "none");
                }
            });
        }
    }
Ibraheem
  • 2,168
  • 20
  • 27