shartshooter
shartshooter

Reputation: 1811

How can I animate divs in order with jQuery?

I'm building a simple Simon Says/Memory app and am trying to use jQuery to animate some divs where the user repeats the pattern back. The part I'm getting stuck on is lighting up the divs in sequence.

My initial thought was to use an array [1,4,2,3,1,4] that matches up with the id's of specific divs

HTML


<div class="container">
  <div id="1" class="red square"></div>
  <div id="2" class="yellow square"></div>
  <div id="3" class="blue square"></div>
  <div id="4" class="green square"></div>
</div>

CSS


.square{
  width: 200px;
  height: 50px;
  opacity: 0.2;
}

.brighten {
  opacity: 1;
}

From here, I'd loop through the array and do something like:

for( var i = 0; i < arr.length; i++){
  var element = $("#"+arr[i]);

  element.addClass('brighten');

  //pause for a half second

  element.removeClass('brighten');
}

being a JS/jQuery novice I'm struggling because the divs are being brighten and unbrighten all at once.

I looked at using jQuery animate() but when I use the following code

for (var i = 0; i < arr.length; i++) {
  $("#" + arr[i]).animate({
    opacity: 1,
  }, 500, function() {
    // Animation complete.
    $("#" + arr[i]).animate({
      opacity: 0.2,
    }, 500, function() {
        // Animation complete.
    });
  });
}

all of the divs are being highlighted at once rather than in sequence.

Upvotes: 1

Views: 2058

Answers (4)

guest271314
guest271314

Reputation: 1

Try using Array.prototype.slice() to make copy of original array, Array.prototype.shift() , recursive call to an IIFE

var arr = [1, 4, 2, 3];

(function animate(elem) {
  var el = elem.shift();
  if (el) {
  $("#" + el).animate({
    opacity: 1
  }, 500, function() {
    $(this).animate({opacity:0.2}, 500, animate.bind(null, elem))
  })
  }
}(arr.slice(0)));
.square {
  width: 200px;
  height: 50px;
  opacity: 0.2;
  display:inline;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<div class="container">
  <div id="1" class="red square">1</div>
  <div id="2" class="yellow square">2</div>
  <div id="3" class="blue square">3</div>
  <div id="4" class="green square">4</div>
</div>


I'm looking to use the function you've created to iterate through several loops. Without repeating code, how could I do that?

Try returning jQuery .promise() from animate function, using recursion to call animate on each item within arrays

var arr = [1, 4, 2, 3];
var arr2 = [2, 3, 2, 3];
var arr3 = [4, 3, 2, 3];
var arrays = [arr, arr2, arr3];
var a = arrays.slice(0);

function animate(elem) {
  var el = elem.shift();
  if (el) {
    // added `return` 
    return $("#" + el).animate({
      opacity: 1
    }, 500, function() {
      return $(this).animate({
        opacity: 0.2
      }, 500)
    // return jQuery promise object from `animate` function
    }).promise().then(animate.bind(null, elem))
  } 
}; //Need to iterate through `arrays` if possible. 
(function cycle() {
  return animate(a.shift())
  // recursively call `cycle` for each item within copy of `arrays`: `a`
  // if `a.length` is not `0`
  .then(a.length && cycle)
}())
.square {
  width: 200px;
  height: 50px;
  opacity: 0.2;
  display: inline;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<div class="container">
  <div id="1" class="red square">1</div>
  <div id="2" class="yellow square">2</div>
  <div id="3" class="blue square">3</div>
  <div id="4" class="green square">4</div>
</div>

jsfiddle https://jsfiddle.net/ry40t266/9/

Upvotes: 1

adeneo
adeneo

Reputation: 318252

The loop runs, and completes, right now, it doesn't wait for your animations, they all start immediately and finish in half a second.

If you want to "stagger" the animation, you have to add a delay that grows with each iteration

for (var i = 0; i < arr.length; i++) {
  $("#" + arr[i]).stop(true, true).delay(i*500).animate({
    opacity: 1
  }, 500, function() {
      $(this).animate({
          opacity: 0.2,
      }, 500, function() {
            // Animation complete.
      });
  });
}

Upvotes: 1

Leo Skhrnkv
Leo Skhrnkv

Reputation: 1703

You can also try to use CSS3 features:

<!DOCTYPE html>
<html>
<head>
<style> 
#para1{
    width: 100px;
    height: 100px;
    background-color: yellow;
    -webkit-animation-name: example; /* Chrome, Safari, Opera */
    -webkit-animation-duration: 1s; /* Chrome, Safari, Opera */
    animation-name: anime1;
    animation-duration: 1s;
}

/* Chrome, Safari, Opera */
@-webkit-keyframes anime1 {
    from {opacity: 0;}
    to {opacity: 0.9;}
}

/* Standard syntax */
@keyframes anime1 {
    from {opacity: 0;}
    to {opacity: 0.9;}
}

#para2 {
    width: 100px;
    height: 100px;
    background-color: green;
    -webkit-animation-name: example; /* Chrome, Safari, Opera */
    -webkit-animation-duration: 5s; /* Chrome, Safari, Opera */
    animation-name: anime2;
    animation-duration: 5s;
}

/* Chrome, Safari, Opera */
@-webkit-keyframes anime2 {
    from {opacity: 0;}
    to {opacity: 0.9;}
}

/* Standard syntax */
@keyframes anime2 {
    from {opacity: 0;}
    to {opacity: 0.9;}
}

#para3 {
    width: 100px;
    height: 100px;
    background-color: red;
    -webkit-animation-name: example; /* Chrome, Safari, Opera */
    -webkit-animation-duration: 10s; /* Chrome, Safari, Opera */
    animation-name: anime3;
    animation-duration: 10s;
}

/* Chrome, Safari, Opera */
@-webkit-keyframes anime3 {
    from {opacity: 0;}
    to {opacity: 0.9;}
}

/* Standard syntax */
@keyframes anime3 {
    from {opacity: 0;}
    to {opacity: 0.9;}
}
</style>
</head>
<body>


<div id="para1"></div>
<p>Text</p>
<div id="para2"></div>
<p>Text</p>
<div id="para3"></div>


</body>
</html>

this generates a 'stoplight' that changes opacity from top to bottom without any JS to begin with. You just change the duration of your effect by using animation-duration attribute. Note: This example does not work in Internet Explorer 9 and earlier versions.

Upvotes: 0

Jason Spradlin
Jason Spradlin

Reputation: 1427

The reason for your issue is that the FOR loop you're using doesn't wait for the first light to brighten/unbrighten before moving along to the next light. You'll need to find a way to chain these behaviors. Since jQuery animate doesn't use a true Promise structure (a discussion for another time), you might be better using the callbacks more effectively. Consider this:

var sequence = [1,4,2,3,1,4];
var currentIndex = 0;

// start first light
$('#' + sequence[currentIndex]).addClass('brighten');

// prepare to dim and move to next light
pauseBeforeNextLight();

/**
 * pauses, then turns off one light and turns on the next.
 */
function pauseBeforeNextLight() {
    window.setTimeout(function() {
        var $oldLight = $('#' + sequence[currentIndex]);
        $oldLight.removeClass('brighten');

        if (currentIndex >= sequence.length) {
            // we've reached the end of the sequence. Discontinue
            return;
        }

        currentIndex++; // same as currentIndex = currentIndex + 1

        // turn on the next light
        var $newLight = $('#' + sequence[currentIndex]);
        $newLight.addClass('brighten');

        // leave the new light on for 0.5s, then next. This is INSIDE the
        // timeout, so it WON'T happen immediately like in a for-loop.
        pauseBeforeNextLight() 
    }, 500);
}

This will force the program to NOT run the next light until the previous light is dimmed. In this example, the first light will dim at the exact time that the next light is brightened, then 0.5s between changes.

Upvotes: 1

Related Questions