Foliotek Development Blog

Getting the width of a hidden element with jQuery using width()

by Tim Banks

UPDATE #3: It needs to be noted that the fix introduced in jQuery 1.4.4 is only for the width() and height() methods. ?If you need inner/outer dimensions the method below still needs to be used. ?I have updated the method to return height/width, outer height/width, and inner height/width. ?There is an optional parameter to include margins in the outer dimension calculations. ?Thanks Ryan and Fred for the heads up.

UPDATE #2: jQuery 1.4.4 was released today (11/11/2010) and included in the release was an update to the width() and height() methods.z ?Each method will now return the correct dimension of the element if it is within a hidden element. ?For further information, you can view the bug report.

UPDATE #1: Based on the feedback in the comments regarding the use of the swap method, I am updating this post with a solution from Ryan Wheale.? He created a function to return the dimensions of an element that is hidden or nested within 1 more hidden elements.? Here is the code that he posted below in his comment:


//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));


This basically performs the same operations as the swap method.? This is safer to use in case the swap method is removed from the jQuery core.

I have tested this in multiple cases and each time the correct results were returned.

Thanks Ryan.

————————————————————————————————————————————-

Original post

I recently ran into a problem with jQuery’s width(). ?The problem is with a visible element that is inside a hidden element will return a value of 0 instead of its’ actual calculated width. ?After messing around with it for a little bit I finally came up with a solution. ?The method I used involved adding some CSS properties to the hidden element. ?The CSS properties involved are position, visibility, and display.


//$HiddenItem is the element that is wrapping the element that you want the width of
//$Item is the element you want the width of

$HiddenItem.css({
    position: "absolute",
    visibility: "hidden",
    display: "block"
})
$Item.width();

$HiddenItem.css({
    position: "",
    visibility: "",
    display: ""
})

After setting the above CSS properties on the element, you can then call width() and the correct value will be returned. After you call the width() method you should clear the properties in order to return the element to the way it was.

Setting the properties to an empty string is probably not the best way to do it though. What if there was a position value already set? Using this method would clear out that initial values of the CSS properties.

I found the swap() method to be handy in this situation. I found this method while looking through the jQuery source code.

 // A method for quickly swapping in/out CSS properties to get correct calculations swap: function( elem, options, callback ) {      var old = {};       // Remember the old values, and insert the new ones      for ( var name in options ) {           old[ name ] = elem.style[ name ];           elem.style[ name ] = options[ name ];      }       callback.call( elem );       // Revert the old values      for ( var name in options ){         ?elem.style[ name ] = old[ name ];      } } 

By using the swap method, the old CSS properties will be remembered and reapplied after finding the width of the element. The swap method takes in 3 parameters:

  1. The element that you would like to swap the CSS properties on
  2. CSS key/value pairs that you want to change
  3. A callback function to call after the properties are set

To rewrite the above to use the swap method I would do the following:


var props = { position: "absolute", visibility: "hidden", display: "block" };
var itemWidth = 0;

$.swap($HiddenItem[0], props, function(){
     itemWidth = $Item.width();
});

//Use itemWidth


I coded up a small example on jsbin. Here is the link http://jsbin.com/ofine3/2.

$HiddenItem.width();

Tags: , ,

« »

25 Responses to “Getting the width of a hidden element with jQuery using width()”

  1. Juan Says:

    Where you are passing “$HiddenItem”, I am passing “$(”#activeChart”)”. I am getting an error that elem.style is null. Any ideas?

    Thanks,
    Juan

  2. Tim Banks Says:

    You need to pass “$(”#activechart”)[0]” instead.

    I updated my post to reflect this change. Please take a look at the post again as I have updated some other parts.

    In my situation, I had an element I needed to get the width of that was inside of a hidden element. Please check the jsbin link for an example of the usage.

    Let me know if that works out.

  3. Juan Says:

    I eventually figured out that I needed the [0] index. I also needed the width of an element inside a hidden element. I ended up just using $tab.removeClass(“ui-tabs-hide”), doing the operation I needed, then used $tab.addClass(“ui-tabs-hide”).

    Thanks for the post. It got me going in the right direction.

  4. Rob B Says:

    Awesome! Exactly what I needed – works great!

  5. Ryan Wheale Says:

    I could kiss you.

  6. Andrea Says:

    Great tip!

    However it”s a non-public function. So instead of using the jQuery one, I would suggest to create a stand-alone version. Who knows if it will get removed from the library at some point in future?
    Also, there”s an opening bracket at the end of line 14.

  7. Ryan Wheale Says:

    After finding this post, I found that I sometimes have elements that are the child of several hidden elements.

    And like Andrea, I did not like that the swap function isn”t really supported in the jQuery documentation.

    So I created this function, which performs the same action as the “swap” function, and it takes care of multiple hidden parents. It could use some testing out in the wild… so please post back here if you find any issues. Hopefully Tim might help support this?? =)

    (function($) {
    $.fn.getHiddenDimensions = function(boolOuter) {
    var $item = this;
    var props = { position: ”absolute”, visibility: ”hidden”, display: ”block” };
    var dim = { ”w”:0, ”h”:0 };
    var $hiddenParents = $item.parents().andSelf().not(”:visible”);

    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.w = (boolOuter === true) ? $item.outerWidth() : $item.width();
    dim.h = (boolOuter === true) ? $item.outerHeight() : $item.height();

    $hiddenParents.each(function(i) {
    var old = oldProps[i];
    for ( var name in props ) {
    this.style[ name ] = old[ name ];
    }
    });
    //$.log(”w: ” + dim.w + ”, h:” + dim.h)
    return dim;
    }
    }(jQuery));

  8. Isaac Says:

    Thank”s Ryan Wheal, your plugin fixes the bug of width.
    This was occupied by me to create a menu Scalable to N number of sub menus because I did not find any online that had the correct operation.

    Thanks again.

  9. Tim Banks Says:

    I agree with you Andrea about worrying about using a non-public function. I will be looking into Ryan”s suggestion below. Also, thanks for the tip about he missing bracket. I have made the update.

  10. Tim Banks Says:

    Thanks for your comment Ryan. I have updated the post to include the function. I tested the function in multiple cases and each time it came back with the correct dimensions.

  11. Ryan Wheale Says:

    Thanks for the recognition Tim. Your original post saved me on a big project I was working on (hence my lude comment above). Glad I could help make it better. Thanks again for the inspiration.

  12. Darian Cabot Says:

    This has been very helpful, thanks!

    I found that even with an elements parent hidden I was able to get dimensions using the .css() function. Problem is I wanted an exact pixel dimension though, not a percentage as the CSS was defined. This solution works great :-)

    I”ve included this in the latest jGauge project build: http://www.dariancabot.com/projects-2/jgauge/

  13. Tim Banks Says:

    I”m glad the post helpful to you Darian.

    I also had noticed that .css() was returning a value on hidden elements and that was how I found the original swap method in the source. Unfortunately it doesn”t give the computed width value that most people are looking for.

  14. Fred Wuerges Says:

    Very very helpful!
    I tried the last function you posted and it worked perfectly!!!
    Thanks for solution.

  15. Fred Wuerges Says:

    In new version of jQuery (1.4.4) this bug is fixed.
    Now, default functions (width, height, outerWidth, outerHeight) return the size of hidden elements.
    But… i tried with this new version and his not return the correct dimensions of element.

    The bug can be viewed in this url:
    http://jsfiddle.net/VERw5/8/#keyboard

  16. Tim Banks Says:

    The post was updated on 11/11 when 1.4.4 came out to alert users this issue has been fixed. Thanks.

  17. Ryan Wheale Says:

    @Fred – This seems to be a bug with outerWidth/outerHeight. I just reviewed the jQuery core and can see that when using outerWidth/outerHeight, jQuery finds the CSS value for width/height and uses parseFloat to return the value.

    So what this means is when you set:
    style=”width:100%”

    jQuery basically does this:
    return parseFloat(”100%”);

    This would result in the width being returned as ”100” as opposed to the computed width. This is nothing new. 1.4.2 uses the same outerWidth/outerHeight code, and I assume earlier versions do as well.

    Having reviewed the core code, I can confirm that they are using the “swap” method (as described in this post) ONLY for width() and height(). It is NOT being used for innerWidth/innerHeight and outerWidth/outerHeight.

    @Tim – maybe you can make a note that the jQuery core 1.4.4 ONLY works with width() and height() – that you must still use *our* plugin if they wish to get the outer dimensions (my modifications did not account for inner dimensions… my bad).

  18. Tim Banks Says:

    I have updated the post to mention the fix in jQuery 1.4.4 is only for the width and height methods. I have also updated the plugin to include outer/inner dimensions. The object returned will include all of the dimensions. It might be overkill to include all the dimensions, but those methods are pretty fast.

  19. sompylasar Says:

    I”ve traced the code of current jQuery 1.4.4 release and found that any of innerWidth/Height, outerWidth/Height and width/height for an element (not window or document) fall into jQuery.css (for inner/outer the ”extra” param is specified as “margin”, “border” or “padding”).

    (line 7117)
    jQuery.each([ "Height", "Width" ], function( i, name ) { // for any width and height

    // innerHeight and innerWidth
    jQuery.fn["inner" + name] = function() {
    return this[0] ?
    parseFloat( jQuery.css( this[0], type, “padding” ) ) : // fall into jQuery.css
    null;
    };

    // outerHeight and outerWidth
    jQuery.fn["outer" + name] = function( margin ) {
    return this[0] ?
    parseFloat( jQuery.css( this[0], type, margin ? “margin” : “border” ) ) : // fall into jQuery.css
    null;
    };

    jQuery.fn[ type ] = function( size ) {

    // Get or set width or height on the element
    } else if ( size === undefined ) {
    var orig = jQuery.css( elem, type ), // fall into jQuery.css
    ret = parseFloat( orig );

    The css method is declared as follows (line 5346):
    css: function( elem, name, extra ) {

    Then css finds a hook for “width” or “height” among cssHooks. This hook uses swap BUT only for the current element (line 5399).

    So, the fix applies to ALL dimensions only if the CURRENT element is hidden. No parents are traversed at all.

  20. Nathan Says:

    Hey, great solution Tim, but is there a license attached to this plugin?

  21. Richard Says:

    Great solution, I appreciate your sharing it!

  22. Gabriel Says:

    I”ve still encountered the bug in jQuery 1.6.4, and your fix worked like a charm, thank you!

  23. hybris Says:

    thanks dude

  24. @ohforfs Says:

    Is this fixed in the latest Jquery or do we still need this workaround? Just checking as it has been a while – thanks ;)

  25. Javi Says:

    Muchas gracias.

    Thanks.

Leave a Reply