jQuery - Get Width of Element when Not Visible (Display: None)
Asked Answered
H

12

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?

Hyetology answered 24/9, 2009 at 15:5 Comment(0)
C
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));
Chelonian answered 24/9, 2009 at 15:19 Comment(9)
Won't this make the browser redraw the page twice? If so, this is a suboptimal solution.Wohlen
@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. jsfiddle.net/67cgB. I'm having the hardest time figuring out what else to could do to fix it.Novara
How can I use this hack inside jquery accordion to get hidden accordion dimensions?Need a your help.Ochlophobia
jQuery doesn't think its visible -> It IS visible when you retrieve the valuesIlluse
@Illuse The element is not visible to the user. Positioning an element absolutely with visible hidden will not cause the element to be shown.Chelonian
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.Illuse
The link is being blocked by the antivirus and google. Does anyone have the referenced code?Chordate
@Chordate I added the code from the referenced blog post.Chelonian
.andSelf() is deprecated in jQuery 1.8+, and removed in jQuery 3+. If you're using jQuery 1.8+, replace .andSelf() with .addBack().Bank
P
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"));
Personally answered 3/7, 2012 at 16:6 Comment(6)
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"); )Phytobiology
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.Beeswing
I modified clone.css("visibility","hidden"); to clone.css({"visibility":"hidden", "display":"table"}); which resolves the issue pointed out by @BeeswingSurcease
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.Amethyst
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 stylesLaevorotatory
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.Childers
H
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();
Horwath answered 10/12, 2012 at 6:21 Comment(2)
YUI version for those who need it: gist.github.com/samueljseay/13c2e69508563b2f0458Huber
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.Decussate
A
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.

Arguseyed answered 24/8, 2012 at 17:30 Comment(0)
S
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

Stefansson answered 20/10, 2013 at 12:5 Comment(1)
CSS is much complex than everyone think This is so true …Giacopo
B
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();
Booth answered 5/10, 2016 at 20:52 Comment(1)
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.Sweat
M
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;
}
Medicable answered 24/9, 2009 at 15:5 Comment(0)
B
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
Buckthorn answered 12/11, 2015 at 19:40 Comment(0)
H
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.

Huber answered 4/6, 2014 at 1:41 Comment(0)
S
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.

Swat answered 16/5, 2013 at 8:29 Comment(0)
C
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.

Churchgoer answered 10/7, 2018 at 11:48 Comment(0)
C
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");
                }
            });
        }
    }
Christianachristiane answered 20/5, 2019 at 15:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.