Even if you give setTimeout
and animations the same value for the duration, the execution order for their callbacks is not actually guaranteed. A simplified reason is as follows:
JS is essentially single-threaded, allowing you to do 1 thing at a time. It has an event loop in which there is a queue of event queues that all receive callbacks for things like network requests, home events, animation events, etc., And of all these, only one starts at a time and runs until end ( Run To Completion Semantic ). A further complication of this single-threaded operation is that things like repaints and garbage collection can also be performed on this thread, so additional unpredictable delays can occur.
Useful resources:
This means that although you delay the transition of the height of an empty element to after scaling the parent element, the duration of this delay is not guaranteed sequentially due to the above factors. Thus, an empty element may be absent when calling callback inside setTimeout
.
If you increase the delay for setTimeout
to a larger value, the empty element height animation will actually occur more often than not, as this will increase the gap between the zoomOut
animation and the code at the beginning of setTimeout
, which means the empty element will be more likely to be in the DOM. before we begin to cross its height.
However, in reality there is no guaranteed way to determine the minimum value for this delay, because every time it can be different.
What you have to do is code in such a way that the order of the animationend
and setTimeout
callbacks does not matter.
Decision
Firstly, you do not need additional empty space, you can perform a zoomOut
animation and height transition to the same element.
One thing you should be aware of is that the css library you are using already sets min-height
.mdl-card
to a specific value ( 200px
), so you need to go to this property because the height of the element may be less is the value. You also want to go to height
so that you can remove the element without any jank. Finally, you need to delay the removal of the item after the animation and transition are finished.
Here's a working solution:
$('section').on('click', 'button', function() { var isAnimationDone = false, isTransitionDone = false; var $item = $(this).closest('.mdl-card'); $item .addClass('animated zoomOut') .one('animationend', function() { isAnimationDone = true; onAllDone(); }); $item .css({ height: 0, 'min-height': 0 }) .one('transitionend', function() { isTransitionDone = true; onAllDone(); }); function onAllDone() { if (isAnimationDone && isTransitionDone) { $item.remove(); } } });
.animated { animation-duration: 300ms } .mdl-card { transition: min-height 300ms, height 300ms; }
<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" /> <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> <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>
With promises, it gets a little easier:
function animate($item, animClass) { return new Promise((resolve) => { $item.addClass('animated ${animClass}').one('animationend', resolve); }); } function transition($item, props) { return new Promise((resolve) => { $item.css(props).one('transitionend', resolve); }); } $('section').on('click', 'button', function() { const $item = $(this).closest('.mdl-card');
.animated { animation-duration: 300ms } .mdl-card { transition: min-height 300ms, height 300ms; }
<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" /> <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> <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>