How can I animate DOM elements in a loop with the interval between each iteration?

I have a foo function, and I wanted to add a sleep / wait function to make the animation look like DOM elements. I have already done some research, and I know that it is not possible to pause the javascript function because it freezes the browser - correct me if I am wrong. How can I overcome it?

 function foo() { while (someCondition) { var $someDiv = $('.someDiv:nth-child(' + guess + ')'); $someDiv.css({'background-color': 'red'}); wait 1000ms $someDiv.css({'background-color': 'blue'}); wait 1000ms if (someCondition2) { doSomething; } else { for loop } } } 

$someDiv refers to different DOM elements with each iteration of the while , since the guess variable changes

What i tried

  • I used the function below and it worked, but the problem is that I could not use the for loop in my asynchronous function foo

     function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } 
  • I tried setTimeout but I could not achieve any valid result.

    If I complete setTimeout this piece of code: ('$someDiv').css({'background-color': 'red'}); , then after a certain amount of time, all $someDiv's change the css style together (remember that $someDiv refers to a different DOM element with each iteration of the while ).

    If I setTimeout piece of code with if , else in setTimeout , then I have an error - Endless loop

Question

The foo function is simplified only to visualize the problem. The original function I'm working on can be found on codepen ( findNumber function)

I want to make a binary search algorithm animation. Something similar to this

How can I achieve the desired result?

In general: how can I animate DOM elements in a loop with the interval between each iteration?

+6
source share
5 answers

The most pleasant and clean solution to this problem is the async / await feature , which will be introduced in a future version of Javascript (ES2017). This allows you to exit the addon. You can create a simple sleep function that looks like this:

 function sleep(time) { return new Promise(resolve => setTimeout(()=>resolve(), time)); } 

You can use this with normal Promise processing:

 sleep(1000).then(()=>console.log('A second later')); 

However, with the async function, you can use the await keyword so that the code expects the promise to be broken before continuing.

 async function doSomething() { await sleep(1000); console.log('A second later'); } 

This means that you can also use a regular loop, including break and continue statements:

 async function doSomething() { let i = 0; while (true) { await sleep(1000); console.log(i); if (++i === 5) break; } } 

This means that your code can be greatly simplified:

 async function foo() { var n = 5; while (n > 0) { n--; var wait = 0; //$('#someDiv').css({'background-color': 'red'}); console.log('doSomething-1'); //wait 1000ms await sleep(1000); //$('#someDiv').css({'background-color': 'blue'}); console.log('doSomething-2'); //wait 1000ms await sleep(1000); if (true) { console.log('doSomething-3'); break; } else { console.log('loop') } } } 

( jsFiddle )

The only problem is that this functionality is far from universal support. Therefore, you need to translate it using software such as Babel .

Note that behind the scenes, your foo function now immediately returns and gives Promise . This Promise allowed with the return value of the function. Therefore, if you want to do more code when foo is completed, you will need to do foo().then(/*callback*/) .

+2
source

You might want to check out jQuery queue() ( fiddle ):

 $("div#someDiv") .queue(function() { console.log('step 1'); $(this).css({ 'background-color': 'blue' }).dequeue(); }) .delay(800) .queue(function() { console.log('step 1'); $(this).css({ 'background-color': 'red' }).dequeue(); }) 

You can also play with Settimeout ( fiddle ):

 var steps = [ function() { $('#someDiv').css({ 'background-color': 'red' }); }, function() { $('#someDiv').css({ 'background-color': 'orange' }); }, function() { $('#someDiv').css({ 'background-color': 'yellow' }); }, function() { $('#someDiv').css({ 'background-color': 'green' }); }, function() { $('#someDiv').css({ 'background-color': 'blue' }); } ]; (function(count) { if (count < 5) { steps[count](); var caller = arguments.callee; window.setTimeout(function() { caller(count + 1); }, 1000); } })(0); 
+2
source

You can do this using jQuery queue () , dequeue () , delay ()

But since you have a closure inside the loop , you will need to use bind () to bind the variables to iterate through the queue.

There is a queue for the body and one queue for each guess , each queue of guesses is put in the queue for deleting objects. Type of loops inside the loops. IMHO could be simpler, it is just to give an idea of ​​queue, delays and binding.

I made some minor changes to your code just to do a demo.

 $(document).ready(function() { var body = $('body'); //buttons click handler functions $('#generateButton').on("click", generateArray); $('#findButton').on("click", findNumber); //enable calling functions by 'enter' key $('#generateInput').keypress(function(e) { if (e.which == 13) { generateArray(); } }); $('#findInput').keypress(function(e) { if (e.which == 13) { findNumber(); } }); //functions function generateArray() { //variables var $generateGroup = $('.generate'); var $generateInput = $('#generateInput'); var $generateInputVal = $generateInput.val(); var $generateButton = $('#generateButton'); var $findInput = $('#findInput'); var $findButton = $('#findButton'); var $arraySection = $('.array-section'); //validation if ($.isNumeric($generateInputVal) && $generateInputVal >= 10 && $generateInputVal <= 100) { //set styling if success $generateGroup.removeClass('has-error'); $generateButton.removeClass('error'); $generateGroup.addClass('has-success'); $generateButton.addClass('success'); //disable generate input group $generateInput.prop('disabled', true); $generateButton.prop('disabled', true); //enable find input group $findInput.prop('disabled', false); $findButton.prop('disabled', false); //clear array section $arraySection.empty(); //generate array = create divs and append them to array section for (var i = 0; i < $generateInputVal; i++) { var $number = $('<div>', { 'class': 'number' }); $arraySection.append($number.text(i + 1)); } } else { // set styling if error $generateGroup.removeClass('has-success'); $generateButton.removeClass('success'); $generateGroup.addClass('has-error'); $generateButton.addClass('error'); } } function findNumber() { //variables var animationSpeed = 5000; var animationCut = 1000; var $generateInput = $('#generateInput'); var $generateInputVal = $generateInput.val(); var $findInput = $('#findInput'); var $findInputVal = $findInput.val(); var min = 0; var max = parseInt($generateInputVal); var guess; var n = 0; var guesses = []; var rejected; // --- binary search loop --- while (max >= min) { n++; //compute guess as the average of max and min guess = Math.ceil((min + max) / 2); console.log("GUESS",guess); //guessed number animation var $guessNumber = $('.number:nth-child(' + guess + ')'); console.log(min + ' ' + max + ' ' + guess); $guessNumber.queue('guessNum', function(next) { $(this).css({ 'background-color': '#000000', 'color': '#ffffff', 'font-weight': 'bold' }); next(); }); $guessNumber.delay(animationCut, 'guessNum'); //await sleep(animationSpeed); //var myVar = setInterval(function(){ $guessNumber.queue('guessNum', function(next) { $(this).css({ 'background-color': 'white', 'color': 'black', 'border': '3px solid #000000' }); next() }); $guessNumber.delay(animationCut, 'guessNum'); body.queue('guessNumbers', function(){ console.log('guessNumbers'); $(this).dequeue('guessNum'); body.dequeue('guessNumbers'); }.bind($guessNumber)); //await sleep(animationSpeed); //if guessed number equals find number then stop if (guess === parseInt($findInputVal)) { //found number animation $guessNumber.queue('guessNum', function(next) { console.log('GOT RESULT', this); $(this).css({ 'color': '#3c763d', 'background-color': '#dff0d8', 'border': '3px solid #3c763d' }); next(); }); body.dequeue('guessNumbers'); break; } //if guessed nsumber is to low, set new min value else if (guess < parseInt($findInputVal, 10)) { rejected = $('.number').slice(min, guess); min = guess + 1; } //if guessed number is to high, set new max value else if(guess > parseInt($findInputVal, 10)) { rejected = $('.number').slice(guess, max); max = guess - 1; } body.queue('guessNumbers',function(){ console.log("rejected",rejected); this.css({backgroundColor: 'red'}); body.dequeue('guessNumbers'); }.bind(rejected)).delay(animationSpeed, 'guessNumbers'); } } }); // function sleep(ms) { // return new Promise(resolve => setTimeout(resolve, ms)); // } 
 html, body { margin: 0 auto; box-sizing: border-box; } .section { margin-top: 40px; margin-bottom: 40px; } .array-section { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; } .input-group { margin: 5px; } .number { display: flex; justify-content: center; align-items: center; width: 40px; height: 40px; text-align: center; margin: 5px; padding: 5px; border: 1px solid gray; border-radius: 3px; transition: all 0.8s; } .error { background: rgb(202, 60, 60); color: white; border: 1px solid rgb(202, 60, 60); transition: 0.5s; } .success { background: rgb(28, 184, 65); color: white; border: 1px solid rgb(28, 184, 65); transition: 0.5s; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="container text-center"> <div class="section section-title"> <h1>BINARY SEARCH</h1> </div> <div class="section input-section"> <div class="col-md-4 col-md-offset-2 col-sm-6 col-sm-offset-3 col-xs-8 col-xs-offset-2"> <div class="input-group generate"> <input type="text" class="form-control input-lg" placeholder="10 - 100" id="generateInput"> <div class="input-group-btn"> <button class="btn btn-default btn-lg" id="generateButton"> Generate array </button> </div> </div> </div> <div class="col-md-4 col-md-offset-1 col-sm-6 col-sm-offset-3 col-xs-8 col-xs-offset-2"> <div class="input-group"> <input type="text" class="form-control input-lg" placeholder="1 - 100" id="findInput" disabled> <div class="input-group-btn"> <button class="btn btn-default btn-lg" type="submit" id="findButton" disabled> Find number </button> </div> </div> </div> </div> <div class="col-xs-12 section array-section"> <div class="number">1</div> <div class="number">2</div> <div class="number">3</div> <div class="number">...</div> <div class="number">n</div> </div> </div> 
+1
source

I am making simple code ... maybe it can help you:

 function foo() { while (true) { var wait = 0; $('#someDiv').css({'background-color': 'red'}); //wait 1000ms wait = wait + 1000; setTimeout(function(){ $('#someDiv').css({'background-color': 'blue'}); }, wait); //wait 1000ms wait = wait + 1000; setTimeout(function(){ if (true) { console.log('doSomething'); } else { console.log('loop') } }, wait); break; } } foo(); 

The secret is in "wait", this value will be the sum of the time you want with the last value of "wait". You will have the expected result, but a different approach.

Hope I helped.

0
source

I will fix the code of Andre Rodriguez:

 var nbCall = 0; function foo() { var wait = 0; $('#someDiv').css({'background-color': 'red'}); //wait 1000ms wait = wait + 1000; setTimeout(function(){ $('#someDiv').css({'background-color': 'blue'}); }, wait); //wait 2000ms wait = wait + 1000; setTimeout(function(){ if (nbCall++ > 5) { // Are other cond console.log('doSomething'); } else { foo(); } }, wait); } foo(); 

jsFiddle: https://jsfiddle.net/nnf8vcko/

0
source

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


All Articles