SetTimeout, jQuery operation, execution completed / started randomly

EDIT: So now it is no coincidence, and it seems that it is always not executed from the .css () method (no changes have been made). Still not getting the mistake I might have made, though.


I am trying to animate a div removal using jQuery and animate.css.

The problem is the events and operations that this animation depends on what is literally randomly executed.

This code is .on("click"... in response to click inside the .on("click"... handler .on("click"... :

 $('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 just goes to the CSS transition, sometimes it ends.

Tested on Firefox and Chromium.

I probably misinterpret something because it seems really strange.

+6
source share
2 answers

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'); // start animation and transition simultaneously const zoomInAnimation = animate($item, 'zoomOut'); const heightTransition = transition($item, { height: 0, 'min-height': 0 }); // remove element once both animation and transition are finished Promise.all([ zoomInAnimation, heightTransition ]).then(() => $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> 
+2
source

As this link (which was provided by someone in the sidebar, I still don’t know how it works?) Thanks, anyway) indicates that setTimeout is not accurate enough for this kind of needs. He ran too fast for the contained code to find a tag that was created about 0.3 seconds earlier.

0
source

Source: https://habr.com/ru/post/1013827/


All Articles