EDIT: So now it's not random and it looks like it always fails to execute from the .css() method (no change were made). Still don't get the mistake I may have made though.
I'm trying to animate the removal of a div with jQuery and animate.css.
Problem is the events and operations this animation depends on literally execute randomly.
This code runs in response to a click
, inside an .on("click"...
handler:
$('section').on('click', 'button', function() {
// Remove the selected card
$(this).closest('.mdl-card')
.addClass('animated zoomOut')
.one('animationend', function() {
empty_space = $('<div id="empty-space"></div>');
empty_space.css('height', ($(this).outerHeight(true)));
$(this).replaceWith(empty_space);
});
// everything is okay until now
// setTimeOut() doesn't always execute
setTimeout(function() {
console.log("test1");
// the following doesn't always happen...
$('#empty-space')
.css({
'height': '0',
'transition': 'height .3s'
// transitionend doesn't always fire either
})
.one('transitionend', function() {
$('#empty-space').remove();
console.log("test2");
});
}, 300);
// Upgrade the DOM for MDL
componentHandler.upgradeDom();
});
/* Animate.css customization */
.animated {
animation-duration: .3s
}
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css" rel="stylesheet" />
<link href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css" rel="stylesheet" />
</head>
<body>
<section>
<div class="mdl-card">
<button class="mdl-button mdl-js-button">Close</button>
</div>
<p>
Content to test the height of the div above
</p>
</section>
<script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</body>
Depending on the page load, nothing happens, sometimes only the first log, sometimes it only gets to the CSS transition, sometimes it's complete.
Tested on Firefox and Chromium.
I'm probably misinterpretating something, cause it seems really strange.
Even though you give
setTimeout
and animation the same duration value, the order of execution for their callbacks isn't actually guaranteed. The simplified reason is the following:JS is essentially single-threaded meaning it can execute 1 thing at a time. It has an event loop that has a bunch of event queues that all accept callbacks for things like network requests, dom events, animation events etc and out of all of these, only one runs at a time and runs until the end (Run To Completion Semantic). Further complications due to this single-threadiness are that things like repaints and garbage collection might also run on this thread so additional unpredictable delays might happen.
Useful resources:
This means that, although you're delaying the height transition of the empty element to after the zooming out of the parent element, the duration of this delay isn't consistently guaranteed due to the above-stated factors. Thus the empty element might not be present when the callback within
setTimeout
gets called.If you increase the delay for
setTimeout
to a larger value , the height animation of the empty element will actually happen more often than not, since this will increase the gap between thezoomOut
animation ending and code withinsetTimeout
starting, meaning the empty element will most likely be in the DOM before we start transitioning its height.However, there isn't really a guaranteed way of determining the minimum value for this delay because each time it could be different.
What you must do is code in such a way that the execution order of the
animationend
andsetTimeout
callbacks doesn't matter.Solution
First things first, you don't need the extra empty space, you can perform both the
zoomOut
animation and height transition on the same element.One thing you must be aware is that the css library you're using already sets
min-height
of.mdl-card
to a certain value (200px
), so you must transition on this property because the height of the element could be less than this value. You also want to transition on theheight
itself so you can remove the element without any jank. Finally, you must delay the removal of the element after both animation and transition are finished.Here's a working solution:
With Promises this becomes a bit easier: