setInterval() to add class then remove class for s

2019-07-28 02:15发布

问题:

I am trying to make a simple 'Simon Game'. I am currently stuck trying to add a class 'on' to a div for one second, then remove that class for the same amount of time.

So I have four divs that I am blinking on and off depending on a sequence of numbers, e.g. [0, 1, 2, 3]. This would blink div[0] on for one second and off for one second then move to div[1], on and off and so on.

Here is my function

/**
 * sequence => array [0, 1, 2, 3]
 * speed    => int 1000 (one second)
 */
playSequence: function(sequence, speed){
    var self = this;

    // removes class 'on'
    view.turnOffPads();
    setTimeout(function(){
        // adds the 'on' class to a specific div
        view.lightUpPad(model.startupSequence[model.count]);
    }, speed / 2);

    setTimeout(function(){
        model.count++; // keeps track of the iterations
        if (model.count < sequence.length) { // if we are still iterating
            self.playSequence(sequence, speed); // light up the next div
        } else {
            model.count = 0; // set back to zero
        }
    }, speed);
},

The problem with this is that I am using two setTimeout functions with one another and although it works I am wondering if there is a better way. If you look I am using a count variable in my model object to keep track of iterations.

Here is my full javascript app so far...

$(function(){

    var model = {
        on: false,
        strictMode: false,
        startupSequence: [0, 1, 2, 3, 3, 3, 2, 1, 0, 3, 2, 1, 0, 2, 1, 3],
        score: 0,
        sequence: [],
        count: 0,
    }

    var controller = {
        init: function(){
            view.cacheDOM();
            view.bindEvents();
        },
        getSequence: function(){
            // get a random number from one to 4
            var random = Math.floor(Math.random() * 4);
            // push it to the sequence array
            model.sequence.push(random);
            console.log(model.sequence);
            // play it
            this.playSequence(model.sequence);
        },

        /**
         * sequence => array [0, 1, 2, 3]
         * speed    => int 1000 (one second)
         */
        playSequence: function(sequence, speed){
            console.log(sequence.length);
            var self = this;

            view.turnOffPads();
            setTimeout(function(){
                view.lightUpPad(model.startupSequence[model.count]);
            }, speed / 2);

            setTimeout(function(){
                model.count++;
                if (model.count < sequence.length) {
                    self.playSequence(sequence, speed);
                } else {
                    model.count = 0;
                }
            }, speed);
            // view.turnOffPads();
        },
    }

    var view = {
        cacheDOM: function(){
            this.$round  = $('#round');
            this.$start  = $('#start');
            this.$strict = $('#strict');
            this.$pad    = $('.pad');

            this.$padArray = document.getElementsByClassName('pad');
        },
        bindEvents: function(){
            this.$start .change(this.start.bind(this));
            this.$strict.change(this.setStrictMode.bind(this));
        },
        start: function(){
            // turn on pads
            if (model.on) {
                this.$pad.removeClass('on');
                this.$round.text('--');
                model.on = false;
                // reset everything
            } else {
                this.$round.text(model.score);
                model.on = true;
                controller.playSequence(model.startupSequence, 100)
                // controller.getSequence();
            }
        },
        lightUpPad: function(i){
            $(this.$padArray[i]).addClass('on');
        },
        turnOffPads: function(){
            this.$pad.removeClass('on');
        },
        setStrictMode: function(){
            if (model.strictMode) {
                model.strictMode = false;
            } else {
                model.strictMode = true;
            }
        }
    }

    controller.init();
});

Is there a cleaner way to add a class, and then remove a class?

回答1:

I think you could turn a list of buttons into a sequence of commands. Then you could use a single setInterval to play commands until it runs out of commands. The setInterval could use 1 second intervals if you want on and off to take just as long, but you could also set it to a little faster to easily allow for different durations.

The example below isn't really based on your code, but just to illustrate the idea. It starts (at the button of the code) with an array of buttons pressed. Those are passed to getSequence. This will return an array of commands, in this case simply the color of the button to switch on, so a sequence could be red,red,red,red,'','','','' to have red light up for 4 'intervals', and then switch it off for 4 'intervals'. This way you could create complicates sequences, and with minor adjustments you could even light up multiple buttons simultaneously.

The interval in the example is set to 1/10 of a second. Each color plays for 10 steps (= 1 second), and each pause plays for 5 steps (= .5 second). In the console you see the basis array of buttons/colors to play, followed by the more elaborate sequence that is played.

// The play function plays a sequence of commands
function play(sequence) {
  var index = 0;
  var lastid = '';
  var timer = setInterval(function(){
    // End the timer when at the end of the array 
    if (++index >= sequence.length) {
      clearInterval(timer);
      return;
    }
    var newid = sequence[index];
     
    if (lastid != newid) {
      // Something has changed. Check and act.
  
      if (lastid != '') {
      // The last id was set, so lets switch that off first.
        document.getElementById(lastid).classList.remove('on');
        console.log('--- ' + lastid + ' off');
      }
      
      if (newid != '') {
        // New id is set, switch it on
        document.getElementById(newid).classList.add('on');
        console.log('+++ ' + newid + ' on');
      }
      lastid = newid;
    }
  }, 100);
}

// generateSequence takes a list of buttons and converts them to a 
// sequence of color/off commands.
function generateSequence(buttons) {
  var result = [];
  for (var b = 0; b < buttons.length; b++) {
    // 'On' for 10 counts
    for (var i = 0; i < 10; i++) {
      result.push(buttons[b]);
    }
    if (b+1 < buttons.length) {
      // 'Off' for 5 counts
      for (var i = 0; i < 5; i++) {
        result.push('');
      }
    }
  }
  // One 'off' at the end
  result.push('');
  
  return result;
}

var buttons = ['red', 'green', 'red', 'yellow', 'yellow', 'blue'];
console.log(buttons);
var sequence = generateSequence(buttons);
console.log(sequence);
play(sequence);
div.button {
  width: 100px;
  height: 100px;
  opacity: .5;
  display: inline-block;
}
div.button.on {
  opacity: 1;
}
#red {
  background-color: red;
}
#green {
  background-color: green;
}
#yellow {
  background-color: yellow;
}
#blue {
  background-color: blue;
}
<div id="red" class="button"></div>
<div id="green" class="button"></div>
<div id="yellow" class="button"></div>
<div id="blue" class="button"></div>



回答2:

Use setInterval instead of setTimeout I have created four divison and blink each division for 1 sec.

var i = 0,
    j = 4;

function blink(i) {
  new Promise(function(resolve, reject) {
  x = setTimeout(function() {
    document.querySelectorAll(".post-text")[i].classList.add("myClass");
    resolve("added");
  }, 1000)
  }).then(function(a) {
  new Promise(function(res, rej) {
    setTimeout(function() {
      clearInterval(x);
      document.querySelectorAll(".post-text")[i].classList.remove("myClass");
      res("deleted");
    }, 1000)
  }).then(function(a) {
    i++;
    if (i < j) {
      blink(i);
    } else {
      i = 0;
      blink(i);
    }
  });

  });
}
blink(i);
.myClass {
  background-color:yellow;
}
<div class="post-text">A</div>
<div class="post-text">B</div>
<div class="post-text">C</div>
<div class="post-text">D</div>



回答3:

TLDR ;)

All you need to do is to use setInterval() or setTimeout() and within the callback function use element.classList.toggle() to add or remove a class from the element.

If/when you want the blinking to stop, just use clearTimeout() or clearInterval().

Here's an example:

var d = document.getElementById("test");
var b = document.querySelector("button");

b.addEventListener("click", function(){
  // Cancel the timer
  clearTimeout(timer);
});

// Assign the timer's ID to a variable so that
// you can stop it later
var timer = setInterval(function(){
  // Toggle the use of the lightUp class
  test.classList.toggle("lightUp");
},1000);
#test {
  width:100px;
  height:100px;
  background-color:blue;  /* <-- blue will be the default color */
}

#test.lightUp {
  background-color:yellow;
}
<div id="test"></div>
<button>Stop Blinking</button>