Search code examples
javascriptarrayscss-animationssettimeout

How do I run clearTimeout for all timeouts inside a forEach loop?


I'm writing Javascript to play a series of repeated animations that go to the notes of a song that plays in the browser. Here is the code I currently have that works:

//Determine the timing for each note played
var expTimes = [0, 215, 323, 645, 860, 968, 1290...]
var strumTimeout;


//Loop through the array and strum at each listed timeout through the duration of the audio

function StrumPattern(strumArray){

//Toggles the CSS transforms to make the strum animation happen 
    function strum(){
        if (strumHand.style.transform == "rotate(50deg)"){
            strumHand.style.transform = "rotate(0deg)";
        }else{
            strumHand.style.transform = "rotate(50deg)";
        }
    }  
   
//Sets the timeout for each strum animation to play in a sequence
    strumArray.forEach(delay => { 
            strumTimeout = setTimeout(() => {
            strum();
            console.log("strum");
        }, delay);
    });
}

However, I want to be able to clear all these timeouts when the user turns off the music. I was able to make the strumming animation stop by adding an if statement around the strum(); function, but it does not stop the forEach loop from running through the array, and if I play the animation again before the array completes, it gets all jittery because it is running two forEach loops at the same time.

I tried using this function below to target all the setTimeouts that I thought would be labeled strumTimeout from the forEach loop. This does not work though, as it gets rid of ALL setTimeout functions on the page, when I have other setTimeout functions that need to run to stop other animations at different points.

//Clears
function clearStrum(){
    var id = strumTimeout;

    while (id--) {
        console.log('removing timeout');
        window.clearTimeout(id); // will do nothing if no timeout with id is present
    }
}

Furthermore, the console.log prints out an increasing number of 'removing timeout' strings each time I activate it

I have also tried using a regular for loop and a break, but it either did not work or it said "Jump target cannot cross function boundary," depending on where I put it.

If there is a way to label the setTimeouts created by the forEach loop and clear or remove them somehow, that is what I am looking for. However, if there is a better way to get animations to play in a sequence with variable timeout times or interval times, I'm curious to hear as well.


Solution

  • You can use Array#map to get all the timeout ids from the loop in an array to be cleared later.

    // outside the function
    let timeouts;
    // inside the function, replace strumArray.forEach with the following:
    timeouts = strumArray.map(delay => setTimeout(() => {
        strum();
        console.log("strum");
    }, delay));
    // cancel all those timeouts later:
    timeouts?.forEach(id => clearTimeout(id));