jQuery - Smooth Animation using slideToggle()
Introduction
For several months, I had been unable to solve a problem with jQuery's animation engine: after adding style declarations to elements, the animation would become "jumpy". Specifically, the shrinking / expanding height was smooth and even for most of the animation, but when about 20% of the animating element remained, it suddenly completely appeared or disappeared.
You can see the issue present in my demonstration of jQuery's slideToggle() function. I am aware that jQuery animations tend to jump when the margin or padding are not 0, but I had worked around that issue by styling elements inside the animating element. Anyway, that should have been fixed with jQuery v1.3 (see the Changes section under Effects). I did manage to narrow the cause down a bit - the animation jump occurred only if the animating element, or a parent, had a width specified.
The Solution
The final fix came from Fen's comments on "Animation Jump - quick tip" at jqueryfordesigners.com. I had to store the height of each animating element (dds, in this case) before it is initially hidden, and then re-apply that height just before slideToggle()'s hiding animation. Here is a simple definition list that demonstrates the solution.
- Lorem Ipsum
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque nisl ante, consequat sed, luctus et, malesuada vitae, erat. Proin in est. Phasellus dui ipsum, blandit non, varius ut, eleifend nec, turpis.
- Cras Eros
- Cras eros nisi, sodales a, consectetur sit amet, consequat ac, libero. Vivamus eget arcu id enim pellentesque euismod. Pellentesque rutrum nunc eu velit. Proin justo turpis, tempor eget, tristique ac, adipiscing eget, sem. Duis leo enim, ornare eu, consequat eu, luctus ut, magna. Sed libero. Proin fermentum tincidunt diam.
- Nullam Sollicitudin
- Nullam sollicitudin libero in mauris. Nam felis mi, aliquam et, pharetra vitae, scelerisque egestas, erat. Praesent molestie, sapien nec hendrerit congue, diam turpis luctus nulla, non dictum neque dolor id leo.
Code
To make the script flexible enough to handle a varying number of elements, I used an array (heightArray) to store and retrieve the appropriate height based on the definition's position in the list. Here is my finished code:
$(document).ready(function(){ // Get height of all dds before hide() occurs. Store height in heightArray, indexed based on the dd's position. heightArray = new Array(); $("dl.v_show_hide dd").each(function(i) { theHeight = $(this).height(); heightArray[i] = theHeight; }); // Hide all dds $("dl.v_show_hide dd").hide(); // When a dt is clicked, $("dl.v_show_hide dt").click(function () { // Based on the dt's position in the dl, retrieve a height from heightArray, and re-assign that height to the sibling dd. $(this).next("dd").css({height: heightArray[$("dl.v_show_hide dt").index(this)]}); // Toggle the slideVisibility of the dd directly after the clicked dt $(this).next("dd").slideToggle("slow") // And hide any dds that are siblings of that "just shown" dd. .siblings("dd").slideUp("slow"); }); });
In the first group of code, I start an array, and then use jQuery's core each() function to walk through my group of dds, and store each height in the array. In the second block, only after I've stored the dd heights, I hide them. In the third block of code, when a dd is clicked, I retrive it's height from the array and re-apply that height just before the animation runs.
I suspect that what I encountered is a jQuery bug - that when a parent width specification exists, the animating element's height is not calculated correctly when animating to non-visible using slideToggle(). I've noticed this behavior with both jQuery 1.3.1 and 1.4.2.
Browser Modifications?
I have tested this in Firefox 3, Internet Explorer 7, and Safari 5 - everything works great with no modifications. Part of the magic of using a library like jQuery is that someone else generally takes care of the cross browser compatibility issues.
Credits
Many thanks to the mysterious Fen for taking the time to describe a solution!