66

I tried to slide in and out a DIV with the toggle function of jQuery but the result is always jumpy at the start and/or end of the animation. Here's the js code that I use:

$(document).ready(function(){
    $('#link1').click(
        function() {
            $('#note_1').parent().slideToggle(5000);
        }
);

And the HTML:

<div class="notice">
    <p>Here's some text. And more text. <span id="link1">Test1</span></p>
    <div class="wrapper">
        <div id="note_1">
            <p>Some content</p>
            <p>More blalba</p>
        </div>
    </div>
</div>

You can also see the complete example here: jQuery Slide test

I usually use Mootools and I can do this slide without any problems with it. But I'm starting a new project in Django and most app in Django use jQuery. So for that and after reading this jQuery vs Mootools I decided that will be a good occasion to start using jQuery. So my first need was to slide this DIV. And it didn't work properly.

I did more search and I found that's an old bug in jQuery with margin and padding applied to the DIV. The solution is to wrap the DIV in another DIV. It didn't fix the thing in my case.

Searching further I found this post Slidedown animation jumprevisited. It fix a jump at one end but not at the other (Test2 in jQuery Slide test).

On Stack Overflow I found this jQuery IE jerky slide animation. In the comments I saw that the problem is with the P tag inside the DIV. If I replace the P tags with DIV tags that fix the problem but that's not a proper solution.

Lastly I found this Weird jQuery behavior slide. Reading it I understood that the problem resolved by switching from P tag to DIV was with the margins of the P (not present in the DIV) and the collapsing of margins between elements. So if I switch the margins to paddings it fix the problem. But I loose the collapsing behavior of margins, collapsing that I want.

Honestly I can say that my first experience with jQuery is not really good. If I want to use one of the simplest effect in jQuery I have to not use the proper function (slideToggle) but instead use some hand made code AND wrap the DIV in another DIV AND switch margins to paddings, messing my layout.

Did I miss a simpler solution ?

As krdluzni suggest, I tried to write as custom script with the animate method. Here's my code:

var $div = $('#note_2').parent();
var height = $div.height();

$('#link2').click(
    function () {
        if ( $div.height() > 0 ) {
            $div.animate({ height: 0 }, { duration: 5000 }).css('overflow', 'hidden');
        } else {
            $div.animate({ height : height }, { duration: 5000 });
        }

        return false;
});

But that doesn't work either because jQuery always set the overflow to visible at the end of the animation. So the DIV is reapearing at the end of the animation but overlaid on the rest of the content.

I tried also with UI.Slide (and Scale and Size). It works but the content below the DIV doesn't move with the animation. It only jump at the end/start of the animation to fill the gap. I don't want that.

UPDATE:

One part of the solution is to set the height of the container DIV dynamically before anything. This solve one jumping. But not the one cause by collapsing margin. Here's the code:

var parent_div = $("#note_1").parent();
parent_div.css("height", parent_div.height()+"px");
parent_div.hide();

SECOND UPDATE:

You can see the bug on the jQuery own site at this page (Example B): Tutorials:Live Examples of jQuery

THIRD UPDATE:

Tried with jQuery 1.4, no more chance :-(

Community
  • 1
  • 1
Etienne
  • 12,440
  • 5
  • 44
  • 50

27 Answers27

50

I found what works consistently is setting an invisible 1px border:

border: 1px solid transparent;

No need to fix the width or height or anything else and the animation doesn't jump. Hooray!

Muers
  • 3,190
  • 3
  • 26
  • 32
  • 3
    This solution works (you can also set the padding to 1px for the same result) in the sens that the animation doesn't jump. But I'm not satisfied with it because, by setting the border or the padding, what it really does, is stopping the collapsing of the margins between childs and parent. And this collapsing is something that I want to keep (see my question). – Etienne Feb 18 '12 at 03:45
  • For people who disabling collapsing margins of the sliding elements is not an issue, this is the best solution I've found. – J. Bruni Feb 27 '14 at 18:42
  • 1
    Even though I don't really understand it - this is a really simple way to fix the jerk :) I'm using Bootstrap 3 – legas Sep 23 '14 at 10:58
  • This is one of those things that doesn't make sense but just works. Thanks man. – Rivers Jun 26 '15 at 14:33
  • 2
    border: 0px solid transparent; will get rid of the issue too and doesn't change the appearance at all. – marblegravy Aug 03 '16 at 22:11
  • @marblegravy doesn't seems to work for me (bootstrap 3), it has to be 0.1px+ (which makes extra space between the elements...) – Ido Jan 10 '19 at 22:11
34

The solution is that sliding div must have the width set in pixels. Do not use 'auto' nor '%'. And you will have great result! The problem is in inline elements thats are in a sliding div.

but if they have width in px the height will be identical. Try it.

Matt
  • 74,352
  • 26
  • 153
  • 180
Jekis
  • 4,274
  • 2
  • 36
  • 42
  • 2
    I tried to set the width in px of the sliding DIV without any success. I tried also to set the width in px for every elements in the sliding DIV and that change also nothing. I always have the jumpy behavior. I'm sure the problem is with the collapsing of margins. I will try jQuery 1.4 and see if the problem persist... – Etienne Jan 16 '10 at 02:18
  • Tried with jQuery 1.4, no more chance :-( – Etienne Jan 16 '10 at 04:22
  • 1
    On jQuery 1.6.2, this problem is still an issue. Thankfully though, this fix still works! – Zach Rattner Jun 01 '11 at 15:50
  • I used jQuery to measure and set widths with success. $('.more').each(function() { $('.content', this).width( $(this).width() ); }); – Matt Dec 23 '11 at 17:04
  • 16
    If you're limited and for some reason cannot set widths, just set a 1px solid transparent border, it works just as well. `border: 1px solid transparent;` – Muers Jan 04 '12 at 21:07
  • I was having this problem with jQuery 1.7.1 and the fix worked. Thanks! – igneosaur Oct 16 '12 at 14:31
  • Worked for me with slideUp/Down and height delimited in px – AlexQueue Dec 29 '12 at 21:18
  • Setting the height via CSS works for toggling slideUp and slideDown. It's not ideal for when the sliding content is dynamic rather than fixed though this answer is no less 100% correct. Thanks, Jenechka. – Dominor Novus Feb 10 '13 at 20:12
  • (Y) I Like This Answer, The problem when no width are set for wrapper, so I have same issue, and Im used a width 100% for wrapper, and its work fine ...jQuery 1.7 – Anees Hikmat Abu Hmiad Nov 19 '14 at 18:53
  • I tried the `border: 1px solid transparent;` trick and it didn't work. This did. – chloe784 Dec 07 '16 at 04:05
  • I had no width set, no display set either. Adding just a width:100% sorted it. – Y.K. Oct 08 '21 at 16:28
23

I've ran into this problem today. I did notice however that disabling all CSS fixed the problem. Also I knew it worked fine before so it must have been recent changes that caused the issue.

It turned out I used transitions in CSS to ease in and out of hovers.

Once these transitions were removed from the elements I was adding everything was fine.

So if you have the same issue, just add these lines to the elements you're adding:

-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
-ms-transition: none;
transition: none;

(I might have abused transitions a bit by not just adding them to the elements I want to have transitions for, but using them for the entire website.)

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Nick
  • 640
  • 6
  • 5
16

Try removing all CSS margins from all the elements. Usually jerky animation comes from margins not being taken into account by the animation framework.

Adam Luter
  • 2,193
  • 1
  • 15
  • 20
  • As I write it, it fix the problem if I remove the margins from the P tag and replace it by paddings. But that's not a proper solution for me because it's messing my layout because I loose the collapsing behavior of the margins. – Etienne Aug 26 '09 at 15:21
  • He has already identified what's causing it, but he stated he wants the margins to be margins. Removing them is against his spec. – krdluzni Aug 26 '09 at 15:22
  • Etienne, you will need to first remove the margins around the element being animated with a style() command, then animate it, then. You would still get jerky behaviour at the top and bottom elements and would have to handle these edge cases specifically. – Adam Luter Aug 26 '09 at 15:33
  • Sorry "then animate it, then" should read: then animate it, then when reappearing you will need to restore the margins after the reappear animation has finished. – Adam Luter Aug 26 '09 at 15:34
  • Adam that could be a solution. But that doesn't look special edge case to me to have P tag with margins inside a DIV that I want to slide. I think the root of the problem is that jQuery set the DIV to display:none at the end of the animation. That remove the DIV from the display with all its margins. See my comment to krdluzni. – Etienne Aug 26 '09 at 15:45
  • In that case, I think you can use the animate method and specify you want the height to be set to 0, rather than 'hide' -- I have not personally used the jquery animations though. – Adam Luter Aug 27 '09 at 13:44
  • It's the margins. I've had this problem a lot. The solution to your layout getting messed up is add a surrounding div and animate that one, the margins on your inside div will still work as planned. The issue is with animations and margins on the animated div! – Jay Croghan Jun 22 '12 at 20:12
  • In addition to margins, padding and other attributes affecting the animation, min-height will also cause the animation to 'jump' because jQuery will first set the element to visible which starts at a height of your min-height value then it expands the element to match contents. – Matthew Aug 13 '14 at 22:41
  • This worked for me while the other answers didn't. I just removed the margin from the parent element and applied it to the immediate children elements to get the same appearance. – Dillon Sep 11 '15 at 18:45
8

Jerking happens when the parent div ".wrapper" in your case has padding.

Padding goes on the child div, not the parent. jQuery is animating height not padding.

Example:

  <div class="notice">
     <p>Here's some text. And more text. <span id="link1">Test1</span></p>
     <div class="wrapper" style="padding: 0">
        <div id="note_1" style="padding: 20px">
           <p>Some content</p>
           <p>More blalba</p>
        </div>
     </div>
   </div>

Hope this helps.

CR Rollyson
  • 1,521
  • 1
  • 13
  • 12
3

I find animate() is the most reliable way to animate anything in jQuery (cross browser at least).

This dynamically wraps the content in a div, then animates the height of that div wrapper by using the height of its inner content.

http://jsfiddle.net/BmWjy/13/

$('a').click(function(event) {
        event.preventDefault();
        xToggleHeight($(this).next());
});

//For each collapsible element.
$('.collapsible').each(function() {
        //Wrap a div around and set to hidden.
        $(this).wrap('<div style="height:0;overflow:hidden;visibility:hidden;"/>');
});

function xToggleHeight(el){
        //Get the height of the content including any margins.
        var contentHeight = el.children('.collapsible').outerHeight(true);
        //If the element is currently expanded.
        if(el.hasClass("expanded")){
                //Collapse
                el.removeClass("expanded")
                        .stop().animate({height:0},5000,
                        function(){
                                //on collapse complete
                                //Set to hidden so content is invisible.
                                $(this).css({'overflow':'hidden', 'visibility':'hidden'});
                        }
                );
        }else{
                //Expand
                el.addClass("expanded").css({'overflow':'', 'visibility':'visible'})
                        .stop().animate({height: contentHeight},5000,
                        function(){
                                //on expanded complete
                                //Set height to auto incase browser/content is resized afterwards.
                                $(this).css('height','');
                        }
                );
        }
}
Dan Bamber
  • 41
  • 2
2

You could write a custom animation using the animate method. This will give you absolute control over all details.

krdluzni
  • 799
  • 2
  • 9
  • 10
  • This would be worth a try. I never use the built in Events unless its a trivial show/hide. – Mike Aug 26 '09 at 15:34
  • It's what I did by implementing the solution shown in "Slidedown animation jumprevisited". Maybe I have to tailor it to deal with the collapsing of the margins. I will try that. JQuery was suppose to be simple and the first simple thing I want to do start to be really complicated. Finally I probably have to reimplement the Mootools solution to slide (no display:none so the DIV stay in place with its margins) in jQuery. – Etienne Aug 26 '09 at 15:40
  • I tried in many ways with the animation method and the problem is always the same. If I use hide/show, this set display to none/block. But with display: none, I lost margins of the DIV. If I use only animate with a height of 0. At the end of the animation jQuery set the overflow to visible, overlaying the content the DIV that is suppose to be hidden. Any way I try, there's always a road block :( – Etienne Aug 26 '09 at 18:33
  • jQuery shouldn't be changing the overflow after a custom animation unless you tell it to... – krdluzni Aug 27 '09 at 16:21
2

css padding and jquery slideToggle doesn't work well together. Try to box out padding.

Bloggerschmidt
  • 123
  • 1
  • 5
2

There are obviously a lot of different solutions to this issue - and depending on your layout, different solutions have different results.

Here was what I had (stripped down)

<div>
   <p>Text</p>
</div>
<div class="hidden">
   <p></p>
</div>

When I would use jQuery to show <div class="hidden">, the margin on the <p> element would collapse with the margin of the <p> element above it.

I thought it was strange since they were in different <divs>.

My solution was to eliminate the margin on the bottom of the <p>. Having a margin on one side prevents the margin from the bottom of the first <p> from collapsing with the top of the second <p>.

This workaround solved my problem, can probably be applied to others, but may not work for all.

DevilBoy
  • 43
  • 6
2

I noticed if you have a <br /> after your container <div> the animation will also be jumpy. Removing this resolved my problem.

Shadow The GPT Wizard
  • 66,030
  • 26
  • 140
  • 208
kingr
  • 41
  • 2
1

There are a lot of suggestions here and a lot of back and forth as to what works. For me, the behavior problem was when the animation of expanding the container would over expand and then bounce back to the correct expansion height (all done as part of the one animation). In way of example, the animation would expand to a height of 500px initially and then retract to 450px. There was no problem with collapse.

The solution that worked was to add to the expanding/collapsing div, a CSS of: white-space: nowrap;

That worked perfectly - smooth expansion to the correct height.

R Snow
  • 59
  • 1
1

You just have to modify the up, down effects in effects.js to have them take into account margins or paddings that may exist and then adjust what they perceive to be the total size of the element to accommodate those values...something along these lines....

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();

//below*
  var paddingtop = parseInt(element.getStyle('padding-top'));
  var paddingbottom = parseInt(element.getStyle('padding-bottom'));
  var totalPadding = paddingtop + paddingbottom;

  if(totalPadding > 0)
  {
   elementDimensions.height = (elementDimensions.height - totalPadding);
  }
//above*

  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || { }));
};
LPL
  • 16,827
  • 6
  • 51
  • 95
  • 3
    Thanks for your answer. I will try this when I get more time. But I can say that I don't agree with the word *just* in "You just have to...". The code that you proposed is far from trivial for a function (slide) that should just works. For me this solution is patching a defect in the jQuery slide funtion. I will propably add a bug to their tracker. – Etienne Oct 16 '09 at 01:03
1

I had the same issue, but not a single one of the proposed solutions worked for me, so I propose a solution that eliminates relying on slideToggle() altogether.

Spark Notes: Load the page as normal, collect the height of each element you want to toggle, store that height in a special data attribute, and then collapse each element. Then it's as easy as changing the max-height between the value in the element's data-height attribute(expanded) and zero(collapsed). If you want to add extra padding and margins, etc to the elements, I recommend storing those in a separate CSS class to add and remove with the max-height property.

Place the jQuery right after the elements you want to toggle and allow them to execute during page load (so you don't have to watch them all load and then collapse).

HTML

<ul id="foo">
   <li>
      <h2>Click Me 1</h2>
      <div class="bar">Content To Toggle Here 1</div>
   </li>
   <li>
      <h2>Click Me 2</h2>
      <div class="bar">Content To Toggle Here 2</div>
   </li>
</ul>

CSS

#foo>li>div.bar {transition: all 0.5s;
   overflow: hidden;}

jQuery

$('#foo h2').each(function(){
   var bar = $(this).siblings('.bar');
   bar.attr('data-height', bar.height()); //figure out the height first
   bar.css('max-height', '0px'); //then close vertically
});
$('#foo h2').click(function(){
   var bar = $(this).siblings('.bar');
   if ( bar .css('max-height') == '0px' ){ //if closed (then open)
      bar.css('max-height', bar.data('height') + 'px');
   } else { //must be open (so close)
      bar.css('max-height', '0px');
   }
});

Here is a working JSFiddle: http://jsfiddle.net/baacke/9WtvU/

baacke
  • 781
  • 9
  • 21
1

The problem is that you are performing the action on the parent, doing this removes the CSS related to that element.

You need to run the slide on your note1, not the parent of note 1.

I had the same issue and fixed it by moving down a level.

bwash70
  • 1,135
  • 2
  • 11
  • 24
1

For me removing the min-height from my container solved the problem.

farlord
  • 121
  • 1
  • 5
1

Try setting the 'position' property of the the container (in this case the .notice div) to 'relative'.

Worked for me. Source: slideToggle height is "jumping"

Community
  • 1
  • 1
Michiel
  • 956
  • 2
  • 7
  • 19
  • I just tried this and it doesn't work either in my case. I tried to set the `position` to `relative` to the container, to the sliding div, to everything, etc. and the animation still jump. – Etienne Feb 18 '12 at 03:26
0

In my case I solved it adding style="white-space:nowrap;" to the element to prevent miscalculations on the jQuery function; no need to set a fixed width unless you need to wrap.

eldarerathis
  • 35,455
  • 10
  • 90
  • 93
Andres
  • 1
0

Make sure you don't have CSS transition rules set globally or on your container or any included elements. It will also cause jerkiness.

Ahmad Alfy
  • 13,107
  • 6
  • 65
  • 99
Cam
  • 1
0

Ran into this issue today, saw this question, and started tinkering based on what I saw here. I solved our jumpy issue by removing the position:relative from the CSS of the containing div. No more weirdness after that. My 2 cents.

Ducain
  • 1,581
  • 3
  • 18
  • 27
0

You might try adding a doctype if you don't have one, it worked for me on IE8 after I found the suggestion here on SO: jQuery slideToggle jumps around. He suggests a strict DTD but I just used the doctype that google.com uses: <!doctype html> and it fixed my problem.

Community
  • 1
  • 1
Andrew
  • 6,231
  • 2
  • 29
  • 37
  • Thanks for the answer. I already have a DOCTYPE (XHTML-Transitional, not strict). But you can see the bug on Jquery own site with in a page that have a DOCTYPE of XHTML-strict here (Example B): http://docs.jquery.com/Tutorials:Live_Examples_of_jQuery – Etienne Oct 10 '09 at 03:06
  • Tried IE8 and Chrome3. I see jumpiness in both and understand what you mean. Looks like an issue with jQuery. – Andrew Oct 10 '09 at 10:15
0

I was using slideDown() like this

$('#content').hide().delay(500).slideDown(500); 

For me, it was the main container #content element. I was having it hidden and then calling slideDown(). I removed the padding property in the CSS and everything worked fine after that. It's usually a margin, padding, or % width, so the easiest method is commenting out each property and testing them 1 by 1 to get your results.

RCNeil
  • 8,581
  • 12
  • 43
  • 61
0

I just learned that this problem can also occur if there are floated elements in the expanding/collapsing element. In that case, a clearfix (clear: both;) at the end (still within) the animated element can get rid of it.

UncleBob
  • 1,233
  • 3
  • 15
  • 33
0

i came across the same bug took days to find a solution. the problem is when the element is hidden jquery is getting the wrong height. top fix it you must get the hight before hiding and use a custom animation to that height. its tricky go here for a better explanation

user654994
  • 133
  • 2
  • 6
  • Unfortunately it doesn't work in my case. It's the technique I used in my `Test 2` in my `jQuery Slide test`. – Etienne Oct 28 '11 at 19:48
0

I had the same problem with 'jerkyness' with divs inside my nav tag - my aim is to show an unordered list on hover of the div (if one exists). My lists are dynamically created so they do not have a fixed value.

Heres the fix:

$("nav div").hover(
  function() { // i.e. onmouseover function

    /****simple lines to fix a % based height to a px based height****/
    var h = jQuery(this).find("ul").height(); //find the height
    jQuery(this).find("ul").css("height", h); 
                         //set the css height value to this fixed value
    /*****************************************************************/

    jQuery(this).find("ul").slideDown("500");
  },
  function(){ // i.e. onmouseout function
    jQuery(this).find("ul").slideUp("500");
  });
});
Kimberly-Ann
  • 27
  • 1
  • 2
-1

I had the same issue. I fixed it by adding this:

.delay(100)

Guess giving it more time to think helps it along?

potts
  • 155
  • 1
  • 14
-1

Adding my solution: turned out my issue was flexbox (only in chrome). I had align-items: baseline; set on the parent element. Set align-self: center; to my slideToggling full-width child element and it cleared it right up. Great use of two hours.

Margo
  • 9
  • 2
-1

For me the solution was, that i had a CSS style definition like following:

* {
    transition: all .3s;
}

Removing this was the solution!

Niklas Raab
  • 1,576
  • 1
  • 16
  • 32