Scott
Scott

Reputation: 25

Unable to add class on a delay with setTimeout()

I am trying to display each of the list item elements on a short delay instead of all at once. Each list item should fade in after the corresponding setTimeout delay, but for some reason it is not working. It only adds the class to the last li element and not any other.

// self-invoking anonymous function
(function() {
  "use strict";
  // Local scope vars (elements)
  var slider, submit, display, list, reset;

  // initialize
  function init() {
    // Grab elements
    slider = document.getElementById("slider");
    submit = document.getElementById("submit");
    display = document.getElementById("display");
    list = document.getElementById("list");
    reset = document.getElementById("reset");
    // display change in value on input changed
    slider.addEventListener("input", updateNum, false);
    // print Fibonacci Sequence
    submit.addEventListener("click", printSequence, false);
    // reset Fibonacci Sequence
    reset.addEventListener("click", removePrevious, false);
    // set inital display value
    slider.value = 27;
    updateNum();
  }

  /*
   * Fibonacci Sequence (http://en.wikipedia.org/wiki/Fibonacci_number)
   *
   * The Fibonacci Sequence is a sequence of numbers where each subsequent number
   * is the sum of the previous two.
   *
   * The function below takes in a number n and returns the Fibonacci sequence to
   * the nth number.
   */

  function fibonacci(n) {
    var i, sequence = [1, 1]; // We start our sequence at [1, 1]
    for (i = 1; i < n; i++)
      sequence.push(sequence[i] + sequence[i - 1]);
    return sequence;
  }

  // Update the slider selection
  function updateNum() {
    display.innerHTML = slider.value;
    console.log("changed");
  }

  // Display the Fibonacci sequence
  function printSequence(e) {
    var i, li, textNode, sequence;
    // prevent default submit behavior
    e.preventDefault();
    // remove any previous sequence elements
    removePrevious();
    // Get Fibonacci sequence
    sequence = fibonacci(slider.value);
    // Create child <li>'s
    for (i = 0; i < slider.value; i++) {
      li = document.createElement("li");
      // insert next Fibonacci sequence element
      textNode = document.createTextNode(sequence[i]);
      li.appendChild(textNode);
      list.appendChild(li);
      // setTimeout delay
      setTimeout(function() {
        li.classList.add("animation");
      }, (i + 1) * 500);
    }
    console.log("submitted");
  }


  function removePrevious(e) {
    if (e)
      e.preventDefault();
    while (list.firstChild)
      list.removeChild(list.firstChild);
    console.log("removed");
  }

  // add event listener for page load
  window.addEventListener("load", init, false);

  /* */
})();
.main {
  display: block;
  width: 300px;
  margin: 0 auto;
  font-family: 'Roboto', sans-serif;
}
h1 {
  font-size: 20px;
}
form {
  border: 1px solid #999;
  border-radius: 5px;
  padding: 20px;
  text-align: center;
}
button {
  width: 100px;
  display: inline-block;
}
.input {
  margin: 20px auto;
}
ul li {
  opacity: 0;
}
.animation {
  transition: opacity .5s ease-in;
  opacity: 1.0;
}
<!DOCTYPE html>
<html>

<head>
  <title>Fibonacci Sequence</title>
  <link rel="stylesheet" type="text/css" href="fibonacci.css">
  <link href='http://fonts.googleapis.com/css?family=Roboto:300' rel='stylesheet' type='text/css'>
  <script type="text/javascript" src="fibonacci.js"></script>
</head>

<body>
  <div class="main">
    <form>
      <h1>Fibonacci Sequence</h1>
      How many numbers in the sequence would you like to calculate?
      <br>
      <div class="input">
        <label for="numbers">Pick a number:</label>
        <br>3
        <input type="range" name="numbers" id="slider" min="3" max="50" step="1" value="27">50
        <br>
        <label for="value">Value:</label><span id="display"></span>
        <br>
      </div>
      <button id="submit">Submit</button>
      <button id="reset">Reset</button>
    </form>
    <ul id="list">
    </ul>
  </div>
</body>

</html>

Upvotes: 0

Views: 828

Answers (1)

jfriend00
jfriend00

Reputation: 707238

This is a common problem with a for loop and an asynchronous function like setTimeout() inside the loop.

The value of the li variable will be different when the actual timeout callback is called (it will be at the terminal value of the loop whatever that is which is why it only operations on the last li you create). There are numerous ways to work around this by creating a closure or function to contain the current value of li separately for each call to setTimeout().

One way to solve it is with an inline IIFE that captures the desired value of li for each iteration of the loop like this:

for (i = 0; i < slider.value; i++) {
  li = document.createElement("li");
  // insert next Fibonacci sequence element
  textNode = document.createTextNode(sequence[i]);
  li.appendChild(textNode);
  list.appendChild(li);
  // setTimeout delay
  // create closure to capture the value of li separately for each iteration
  (function(item) {
    setTimeout(function() {
      item.classList.add("animation");
    }, (i + 1) * 500);
  })(li);
}

Upvotes: 1

Related Questions