6

I have a function foo and I wanted to add a sleep/wait function to make a kind of DOM elements animation. I've already done some research and I know that it's impossible to pause a javascript function because it freezes browser - correct me if I'm 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 element with each while loop iteration because variable guess changes

What I've tried

  • I used the function below and it worked but the problem is I couldn't use a for loop in my async function foo

    function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
    }
    
  • I tried setTimeout but I wasn't able to achieve any valid result.

    If I wrap in setTimeout this piece of code: ('$someDiv').css({'background-color': 'red'}); then after specified amount of time all $someDiv's change css style together (keep in mind that $someDiv refers to different DOM element with each while loop iteration).

    If I wrap in setTimeout a piece of code with if, else statements then I've got an error - Infinite Loop

Question

The foo function is simplified just for visualisation the problem. The original function I'm working on you can find 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 interval between each iteration?

Arkej
  • 2,221
  • 1
  • 14
  • 21
  • Don't use while, use `setInterval()` and `clearInterval()` with a memory-like variable that keeps track of the current status. – roberrrt-s Dec 22 '16 at 12:15
  • is `$someDiv` a variable or just a string?? in code you have `$('$someDiv')` which means its a selector.. (its invalid anyways). But in some points you say it changes in each iteration.. did you mean `$someDiv` as a jquery object? – Rajshekar Reddy Dec 22 '16 at 12:27
  • @Roberrrt `setInterval()` will iterate entire function, however I need a few pauses inside it, in specific places – Arkej Dec 22 '16 at 12:30
  • @Arkej you didnt answer my question.. – Rajshekar Reddy Dec 22 '16 at 12:39
  • @Rajshekar Reddy sorry for mistake in code, I've edited my question, now it should be clear – Arkej Dec 22 '16 at 12:42
  • @Arkej so the only thing that is changing is the `guess` right.. which inturn changes the $someDiv – Rajshekar Reddy Dec 22 '16 at 12:45
  • @Rajshekar Reddy yes, exactly – Arkej Dec 22 '16 at 12:48
  • 1
    @Arkej does this smaple help.. https://jsfiddle.net/RajReddy/e8u3qd0z/8/ ? – Rajshekar Reddy Dec 22 '16 at 14:02
  • @Rajshekar Reddy I also tried to divide `foo` into separate functions but I didn't succeed. Your code seems to work as I expected but I have to plug into it. Thank you, I appreciate your effort – Arkej Dec 22 '16 at 14:23

5 Answers5

2

You might want to check 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 could also play around 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);
user286089
  • 172
  • 7
  • thank you for answer, but see on the console: it displays `doSomething-1` 5 times, then after 1 second `doSomething-2` x5 and finally `doSomething-3` x5. I need `doSomething-1`x1 -> 1s -> `doSomething-2`x1 -> 1s -> `doSomething-3`x1 -> 1s - and all of it should repeat 5 times, so it should take 15 seconds in this particular case – Arkej Dec 22 '16 at 13:34
  • 1
    @Arkej I added different suggestions, please check. – user286089 Dec 22 '16 at 13:47
  • Just like @Fefux you've deleted the while loop. My foo function need to have while loop with condition that affects number of iteration. However the concept with jQuery `queue()` seems to be interesting. Thank you for your effort to help me – Arkej Dec 22 '16 at 14:32
2

The nicest, cleanest solution to this problem is with the async/await feature that will come in a future version of Javascript (ES2017). This allows you to get out of callback hell. You can create a simple sleep function that looks like this:

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

You could use this with normal Promise handling:

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

However, with the async functionality you can use the await keyword to make the code wait for the promise to be resolved before continuing.

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

This means that you can use a normal loop as well, 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 dramatically 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 has far from universal support. You therefore need to transpile it using software like Babel.

Note also that, behind the scenes, your foo function now returns immediately and gives a Promise. That Promise is resolved with the return value of the function. So if you wanted to do more code when foo was completed, you would have to do foo().then(/*callback*/).

lonesomeday
  • 233,373
  • 50
  • 316
  • 318
  • 1
    To be honest I'm a little but confused 'cause I've tried this solution (as I wrote on my question) and there were some problems. Now I try again with your hints and it works. Maybe I made some typoo earlier or `sleep` function I used was different than your's one. Anyway, your answer solved my problem. Thank you for your effort – Arkej Dec 22 '16 at 14:19
  • `async/await` looks really interesting :) – sabithpocker Dec 22 '16 at 17:36
1

You can do this by Using jQuery queue(), dequeue(), delay()

But as you have sort of closures inside loop, you will have to use bind() to bind the variables per iteration into the queued functions.

Here there is a queue for body and one queue each for each guess, Each guesses' queue is dequeued in body dequeues. Kind of loops inside loops. IMHO there can be easier ways, this is just to give an idea on queue, delays and binding.

I've made some minor changes to your code, just to make the demo work.

$(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>
Community
  • 1
  • 1
sabithpocker
  • 15,274
  • 1
  • 42
  • 75
0

I make a simple code... maybe 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 the "wait", this value will be the sum of the time you want, with the last "wait" value. You will have the expected result but another approach.

I hope I have helped.

Andre Rodrigues
  • 253
  • 3
  • 6
  • thank you for your answer, although it doesn't solve my problem. You've placed the `break` statement in such way that `while` loop run just once. I need `break` of while loop in one of `if`, `else` conditions but it causes error: [jsfiddle](https://jsfiddle.net/dz10ct4d/1/) – Arkej Dec 22 '16 at 13:04
0

I correct the Andre Rodrigues code :

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/

Fefux
  • 964
  • 5
  • 12
  • thanks for answer, but you've deleted the `while` loop. My `foo` function need to have `while` loop with condition that affects number of iteration - as in my question – Arkej Dec 22 '16 at 13:47