Here is the fiddle https://jsfiddle.net/8p2jjr18/
The idea is to make a fading-in and fading-out rotation of testimonials in vanilla JS. The problem is that when the function runs for the fourth time in setTimeout, the first and subsequent elements in selection do not get the .fade
class. Instead, the .hidden
class gets applied right away (instead of waiting for the .fade
class to be applied and the animation on the class to end) and it messes up the whole picture.
I've tried to place break;
into the end of for
loop instead of the end of if
statement (see example below), but that breaks everything completely (just only one iteration happens), I have no idea why.
function rotateTestimonials() {
for (var i = 0; i < testimonials.length; i++) {
if (testimonials[i].className === "testimonial show") {
testimonials[i].className = "testimonial fade";
testimonials[i].addEventListener("animationend", function () {
testimonials[i].className = "testimonial hidden";
if (i + 1 !== testimonials.length) {
testimonials[i+1].className = "testimonial show";
}
else {
testimonials[0].className = "testimonial show";
}
}, false);
};
break;
};
}
I have two questions:
Why can't I place the break
instruction into the end of for
loop?
Why the function does not work as expected on the fourth and later iterations of setTimeout
cycle?
With your current code, as time progresses you continue adding animationend event listeners resulting in several event listeners on each testimonial element. What you need to do instead is attach just a single event listener which take the appropiate action based on the elements current state.
There are two ways you can handle this. First is creating an event listener for each element.
function createEventListener(i, testimonials){
return function(){
if (testimonials[i].className === "testimonial show"){
testimonials[i].className = "testimonial fade";
} else {
testimonials[i].className = "testimonial hidden";
testimonials[(i+1)%testimonials.length].className = "testimonial show";
}
}
}
var testimonials = document.getElementsByClassName("testimonials")[0].getElementsByClassName("testimonial");
for (var i = 0; i < testimonials.length; i++) {
testimonials[i].addEventListener("animationend", createEventListener(i, testimonials), false);
}
Here each element is given it's own event listener function. When the show animation ends this function is triggered and the element is given the fade class. When the fade animation ends the function is triggered again and the element is hidden and the next element is given the show class. See updated fiddle
The other method is to give a single event listener to the parent element. This function will be triggered whenever a child element fires the animationend event due to event bubbling.
var testimonials = document.getElementsByClassName("testimonials")[0].getElementsByClassName("testimonial");
var i = 0;
document.getElementsByClassName('testimonials')[0].addEventListener('animationend', function(){
if (testimonials[i].className === "testimonial show"){
testimonials[i].className = "testimonial fade";
} else {
testimonials[i].className = "testimonial hidden";
i = (i+1)%testimonials.length;
testimonials[i].className = "testimonial show";
}
});
Here we only have a single event handler which will be called on each childs animation event. It functions the same as above, checking the current element's state and changing accordingly. See updated fiddle