Fixing jQuery's slideDown() effect (i.e. Jumpy Animation)

Posted by Dan on Apr 21, 2009 @ 6:07 PM

[UPDATED: Wednesday, April 22, 2009 at 8:43:54 AM]

I was working on some code today and was using the jQuery slideDown() and slideUp() effects and was running into an issue if the height of my box wasn't greater than certain height. As the box would slideDown, I'd see this jump in the animation as the height originally grew too large, so when the animation finished and it would go to the original height, I'd see this "jumping" effect.

Remembering that I read a recent post by Remy Sharp on the subject, a quick Google search brought up Remy's SlideDown Animation Jump Revisited post.

This turned out to be the same issue I was experiencing. Since I needed a little more generic version of Remy's code, I modified his original source to come up with this workaround:

// this is a fix for the jQuery slide effects
function slideToggle(el, bShow){
  var $el = $(el), height = $el.data("originalHeight"), visible = $el.is(":visible");

  // if the bShow isn't present, get the current visibility and reverse it
  if( arguments.length == 1 ) bShow = !visible;

  // if the current visiblilty is the same as the requested state, cancel
  if( bShow == visible ) return false;

  // get the original height
  if( !height ){
    // get original height
    height = $el.show().height();
    // update the height
    $el.data("originalHeight", height);
    // if the element was hidden, hide it again
    if( !visible ) $el.hide().css({height: 0});
  }

  // expand the knowledge (instead of slideDown/Up, use custom animation which applies fix)
  if( bShow ){
    $el.show().animate({height: height}, {duration: 250});
  } else {
    $el.animate({height: 0}, {duration: 250, complete:function (){
        $el.hide();
      }
    });
  }
}
UPDATE 2009-04-22:
  • Added check so that if requested toggle state is the same as current state, no animation occurs

While I could have written this as a plug-in, I kept it as a function—mainly because this issue should be fixed in the jQuery core soon (if it's not fixed already in the SVN repository.)

To use the solution, just pass in a selector to a unique element (such as "#element-id") as the first argument. The second argument is optional and can be used to force the direction. Use true to force it to slideDown() or false to force it to slideUp(). If the argument is left off, it'll toggle the state of the element.

Anyway, thanks to Remy for figuring out this issue. I'm sure I would have spent way more time tracking down the glitch if it weren't for the fact that I remember reading this when he posted the solution.

NOTE:
If you're element changes dynamically changes height, this solution won't work for you—because it stores the original height and always uses that. You could workaround that by using the data() method to kill the stored height (i.e. $("#your-element").data("originalHeight", null).)
Categories: jQuery

27 Comments

  • Jason Persampieri's Gravatar
    Jason Persampieri
    Very nice timing, I needed this for my project. I had to make two changes though, since my element's height can change (while it's invisible).

    I removed all references to originalHeight. And in the complete function, I changed "$el.hide();" to "$el.hide().css("height", "");"

    Thank you!
  • How do i use this? Just put it in a .js file and link to it? Or put it in my jquery code for what I'm having issues with? Thanks!
  • sweet exactly what I'm looking for.. Need it for my Cart hierarchy categories work well in FF but bad in IE.. Nice Work.
  • A simpler fix is to set the height in the css --

    .foo {height:100px; overflow: hidden;}

    -- and deal with the overflow however you need to, based on your content. Otherwise, the slideDown/slideUp is going to base the slide on the height of the content, and only add the padding at the end of the animation. That's what gives you the jump at the end.
  • Great!
  • Hey,
    I am trying to use the slide up/down thing. everything works great except that the div that is supposed to slide down, grows down along with sliding. How do I get through it?
    Have a look at:
    http://jquerybox.com/index.php/topic,683.0.html
  • This is still not fixed -- using jQuery 1.4.1!

    Maybe they didn't get the memo?
  • thanks. i can use this.
  • thenks
  • I ran into problems with IE8 and slideDown() recently, where IE8 was ignoring a height explicitly declared in the CSS and expanding the animated element to 100%+padding of the parent element before reverting to it's 'real' height. However after a bit of poking around I noticed that IE8 did obey max-height when using slideDown(), which may be of use to somebody out there trying to work around this issue.
  • thanks for you solution. Works like a charm. Just had to make a change to the height to 1px so it won't "Jump" open on the first run. Setting it to '1px' fixed it for me
  • I had the same problem and tried to solve via css only.
    I knew my elements had to have a height somehow. But i didn't want to apply a height because I didn't knew what was the content!

    I didn't want to add all that scripting too! (sorry =)

    Anyway:
    Try to apply a fixed width on you elements to slide down.
    .my-element-to-slide { width: 900px; }

    that fixed it for me on IE7 :)

    \o/
  • Excellent code, works perfect for me! You should really get in touch with the jQuery guys to make this change to the core, this has been a bug for a very long time (and still is as of 1.4.2).
  • Are you all sure it's a bug of jQuery?

    Thinking of the "browser war" regarding margins, paddings and borders that are handled differently on multiple browsers, I get the feeling that the "jump" we all are seeing is simple the "height" of either the top or bottom margins/paddings.

    Funny thing is, if you strip the margin and padding stuff from the element in question, there is no such problem. You just have to make sure that the last child element does not use bottom margin or padding either and you're set.

    Anyway, measure the distance of the jump and you'll notice it's the same amount of pixels your vertical margin and/or padding values use.

    Just my 2 cents...
  • @Mike:

    If the problem wasn't fixable using JavaScript, then I'd indeed say it's a browser issue. However, since you can work around the pretty easily in the code and the one of the things jQuery is trying to solve is simplifying creating these type of effects, I do consider it a bug.
  • Try using overflow:hidden on the object.
  • This might be a simpler fix:

    $('.yourselector').each(function() {
      $(this).height($(this).height()).hide();
    });

    It sets the height of each matched element explicitly to its current calculated height, meaning that you don't have to define the element's height in CSS. Afterwards the element is hidden, awaiting the slide effect.
  • This is great. Thanks.

    I had an issue that it wasn't working the second time an element was clicked so I commented out this:

    // get the original height
    //if( !height ){
      // get original height
      height = $el.show().height();

      // update the height
      $el.data("originalHeight", height);

      // if the element was hidden, hide it again
      if( !visible ) $el.hide().css({height: 0});

     //}


    This forces the function to check the height everytime.
  • just fixed that to work with multiple elements in different sizes

    function slideToggle(element, bShow, duration) {
        var $el = $(element)
        , visible = $el.is(":visible");

        // if the bShow isn't present, get the current visibility and reverse it
        if (arguments.length == 1) bShow = !visible;

        // if the current visiblilty is the same as the requested state, cancel
        if (bShow == visible) return false;

        $.each($el, function (i, e) {
          var $e = $(e);
          var visible = $e.is(":visible");
          var height = $e.show().height();
          if (!$e.data("originalHeight")){
            $e.data("originalHeight", height);
          };
          if (!visible) $e.hide().css({ height: 0 });
        })

        // expand the knowledge (instead of slideDown/Up, use custom animation which applies fix)
        if (bShow) {
          $.each($el, function (i, e) {
            $e = $(e);
            $e.show().animate({ height: $e.data("originalHeight") }, { duration: duration });
          })
        } else {
          $el.animate({ height: 0 }, { duration: duration, complete: function () {
            $el.hide();
          }
          });
        }
      }
  • This was extremely useful, thanks.
  • Erik was correct's Gravatar
    Erik was correct
    Erik's suggestion to use "overflow:hidden;" on the element that's being toggled works fine – no JS needed, no need to guess about content height and padding.
  • Re: "If the problem wasn't fixable using JavaScript, then I'd indeed say it's a browser issue." (http://blog.pengoworks.com/index.cfm/2009/4/21/Fix...)

    I'm not the kind of person to point people to errors, but the "bug" or "issue" can be reproduced with javascript disabled too, voiding your statement.

    I advise you to dive into the several "Box-Sizing bug" articles that are available on the internet since somewhere in the 90s. (One of the reasons IE6 was "loved" so much). Do some historical research on that one and compare individual, cross-version and cross-vendor browser behavior with the W3C specs.

    If you expect jquery as a framework to work around such browser bugs, you are in fact having an issue with a "missing feature". Calling a "missing feature" a "bug" is like telling you you are "wrong" instead of "misinformed". ;)
  • I use that slideDown and slideUp and it works ok. just take a look if you have set width for toggled element
  • IE8 NEED a width declaration for slideDown to work.
  • Just letting people know unfortunately the above solutions didn't work for me, but I realized I forgot to set a DOCTYPE and once I did it worked beautifully.

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitiona...;
    <html xmlns="http://www.w3.org/1999/xhtml">;
  • Figured out one trick. If you have padding on the div you're sliding, it will jump. Put the padding on a div inside.
  • @Shane Stebner's way worked while here.

Comments for this entry have been disabled.