Sequential CSS animation using JavaScript

I am trying to use JavaScript to trigger CSS animations one after another using the animationend event listener. It works, but I would like the animation to be triggerable multiple times. Any advice? The start of the sequence is triggered with a button. I would like to be able to trigger the sequence every time the button is clicked but it currently cycles through only once. All of the code is on codepen

https://codepen.io/cssgrid/pen/BRzdLy/

It is very possible that the JavaScript could be written in a better way!

The problem is that you’re only removing the animationName of text[0], so in nextAnimate() just change

text[0].style.animationName = "";

to

this.style.animationName = "";

BTW, another issue with your code is that you’re re-attaching those animationend listeners within each cycle, so that a lot of redundant handlers are piling up. This doesn’t cause any immediate problems in this case, but it’s of course bad for performance and a waste of memory – each handler still gets called, but does nothing but adding yet another listener. So you should only add one handler to each element:

var text = document.querySelectorAll("h1");
var btn = document.querySelector("button");

text = Array.from(text)

text.forEach(function (current, index) {
  current.addEventListener('animationend', function () {
    var next = text[index + 1]
    
    current.style.animationName = ''
    
    if (next) {
      next.style.animationName = 'ott'
    }
  })
})


btn.addEventListener("click", function() {
  text[0].style.animationName = "ott"
})

Thank you, that is a huge improvement.

What is the benefit of the if (next) { } conditional statement rather than just having next.style.animationName = 'ott' without a conditional?

If you don’t test the next item exists (text[index + 1]) you will get an error where you try to access the style of an element that doesn’t exist.

Uncaught TypeError: Cannot read property ‘style’ of undefined
at HTMLHeadingElement.

I wouldn’t be happy with the multiple h1s and would rather use spans inside an h1 for the text and instead of accessing the style element through script put it back in the css where it belongs and just add a class.

Also the animation isn’t working in Firefox or Edge so I would change the css to this.

e.g.

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
<style>
body, html {
	margin: 0;
}
body {
	font-family: helvetica, sans-serif;
}
h1{
	margin:80px 0;
	text-align:center;
	position: relative;
	height:5em;
	overflow:hidden;
}
h1 span {
	position: absolute;
	top:50%;
	opacity:0;
	margin:auto;
	left:0;right:0;
	letter-spacing:2px;
}
h1 .text-animate{
	animation:text-animate 1s 1 ease-in-out;
}
@keyframes text-animate {
 50% {opacity: 1;}
 100% {
 	opacity: 0;
 	transform: scale(2.5);
 	letter-spacing: 11px;
	}
}
button {
	all: unset;
	letter-spacing: .5px;
	font-size: 1.2em;
	background-color: rgb(10, 200, 90);
	border: solid 1px rgb(0, 150, 50);
	margin:150px  auto 0;
	display: block;
	padding: 10px;
	color: white;
	font-weight: 600;
	border-radius: 5px;
	cursor:pointer;
}
</style>
</head>

<body>
<h1 class="animate"><span>Get</span><span>Ready</span><span>For</span><span>CSS</span><span>Animation</span></h1>
  <button>TRIGGER ANIMATION</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> 
<script>
var text = document.querySelectorAll("h1 span");
var btn = document.querySelector("button");

text = Array.from(text)

text.forEach(function (current, index) {
  current.addEventListener('animationend', function () {
    var next = text[index + 1]    
    current.classList.remove("text-animate");
    if (next) {
      next.classList.add("text-animate");
    }
  })
})
btn.addEventListener("click", function() {
  text[0].classList.add("text-animate");
})

</script>
</body>
</html>

Just for fun and interest’s sake here’s a css only method (no JS at all) and is not meant as a solution to the question as the behaviour is not automatic.

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
<style>
body, html {
	margin: 0;
}
body {
	font-family: helvetica, sans-serif;
}
h1{
	margin:80px 0;
	text-align:center;
	position: relative;
	height:5em;
	overflow:hidden;
}
h1 span {
	position: absolute;
	top:50%;
	opacity:0;
	margin:auto;
	left:0;right:0;
	letter-spacing:2px;
}
.start span{display:none}
#start:checked + .animate .anim{animation:text-animate 1s ease-in-out;}
#start:checked + .animate .anim2{animation-delay:1s}
#start:checked + .animate .anim3{animation-delay:2s}
#start:checked + .animate .anim4{animation-delay:3s}
#start:checked + .animate .anim5{animation-delay:4s}

#start:checked ~.start b{display:none}
#start:checked ~.start span{display:inline-block}

@keyframes text-animate {
 50% {opacity: 1;}
 100% {
 	opacity: 0;
 	transform: scale(2.5);
 	letter-spacing: 11px;
	}
}

#start{width:0;height:visibility:0;overflow:hidden;position:absolute;left:-99em;}
.start{
	font-size: 1.2em;
	background-color: rgb(10, 200, 90);
	border: solid 1px rgb(0, 150, 50);
	margin:150px  auto 0;
	display: table;
	padding: 10px;
	color: white;
	font-weight: 600;
	border-radius: 5px;
	cursor:pointer;
	text-transform:uppercase;
}
</style>
</head>

<body>
<input type="checkbox" id="start">
 <h1 class="animate"><span class="anim anim1">Get</span><span class="anim anim2">Ready</span><span class="anim anim3">For</span><span class="anim anim4">CSS</span><span class="anim anim5">Animation</span></h1>
<label class="start" for="start"><b>Trigger Animation</b> <span>Click twice to re-start animation</span></label>
</body>
</html>

I wouldn’t use the above for mobile as older android still has problems with the general and adjacent sibling selectors (although there are some fixes around). I only post the example to show that in some limited situations js can be avoided.

2 Likes

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.