Majestic Pixel
Majestic Pixel

Reputation: 121

How can I restart a CSS transition as soon as it ends using standard JavaScript?

I have built a kind of password generator that should display a new password whenever the countdown expires. Unfortunately, I have only managed to figure out how to run my code once. The countdown consists of a simple CSS transition, which I would like to keep, because it is much smoother than my other attempts, wherein i tried to repeatedly update the width using JavaScript.

var dictionary = {
	"adverbs": [
  	"always",
  	"usually",
    "probably"
  ],
  "adjectives": [
  	"useful",
  	"popular",
   	"accurate"
  ],
  "nouns": [
  	"computer",
    "software",
    "terminal"
  ]
};

function capitalizeFirst(string) {
	return string.charAt(0).toUpperCase() + string.slice(1);
}

function randomIndex(object) {
	return object[Math.floor(Math.random() * object.length)];
}

function generatePassword() {
	var category = ["adverbs", "adjectives", "nouns"];
	var password = [];
  
  for (i = 0; i < category.length; i++) {
  	password.push(capitalizeFirst(randomIndex(dictionary[category[i]])));
  }
  
  password.push(Math.floor(Math.random() * 8999) + 1000);
  return password.join("");
}

function updatePassword() {
	document.getElementById("countdown-fill").style.width = 100 + '%';
	document.getElementById("text-field").value = generatePassword();
	document.getElementById("countdown-fill").style.width = 0 + '%';
}

setInterval(updatePassword, 5000);
@import url('https://fonts.googleapis.com/css?family=Nunito&display=swap');

body {
  margin: 0;
  width: 100%;
  height: 100%;
  background-color: #f8f8f8;
}

.container {
  max-width: 400px;
  margin: 0 auto;
}

#text-field {
  font-size: 15px;
  font-weight: 400;
  font-family: 'Nunito', sans-serif;
  margin-top: 100px;
  margin-bottom: 10px;
  width: 100%;
  height: 30px;
  padding: 10px;
  box-sizing: border-box;
  border: 1px solid  #e5e5e5;
  background-color: #ffffff;
}

#countdown-background {
  width: 100%;
  height: 10px;
  box-sizing: border-box;
  border: 1px solid  #e5e5e5;
  background-color: #ffffff;
}
#countdown-fill {
  width: 100%;
  height: 100%;
  transition: width 5s;
  transition-timing-function: linear;
  background-color: #1e87f0;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Password Generator</title>
  </head>
  <body>
    <div class="container">
      <input id="text-field" type="text" spellcheck="false">
      <div id="countdown-background">
        <div id="countdown-fill"></div>
      </div>
    </div>
  </body>
</html>

Currently, I have two apparent issues with my code:

  1. The transition becomes delayed due to setInterval. This is not the case if I simply call updatePassword on its own.
  2. The CSS transition only animates once. I would like to reset the animation every time i call updatePassword.

I came across a few jQuery solutions for my problem, but I am not very interested in those, as I want to rely on standard JavaScript as much as possible. However, I am okay with alternative CSS tools like keyframes, which seem to work well:

#countdown-fill {
  width: 100%;
  height: 100%;
  animation: refresh 5s infinite;
  background-color: #1e87f0;
}

@keyframes refresh {
    from {
        width: 100%;
    }
    to {
        width: 0;
    }
}

Although, I do worry about synchronization issues as the animation is not coupled with updatePassword in any way.

Question: Is there a way to have updatePassword reset the animation each time I call the function, and remove the initial delay?

JSFiddle: https://jsfiddle.net/MajesticPixel/fxkng013/

Upvotes: 4

Views: 977

Answers (2)

lawrence-witt
lawrence-witt

Reputation: 9354

I second what NevNein has posted, and would also like to add that if you want to couple the transition with updatePassword so that they have a linked relationship and not just matched timeouts, you should replace setInterval(updatePassword, 5000) with:

updatePassword();
document.getElementById('countdown-fill').addEventListener("transitionend", updatePassword)

The countdown and password change will now run at any speed you set in the CSS.

Upvotes: 1

NevNein
NevNein

Reputation: 547

I've modified your JSFiddle, here's the explanation.

#countdown-fill {
  width: 100%;
  height: 100%;
  transform-origin: left;
  background-color: #1e87f0;
}
.reset {
  transition: transform 5s linear;
  transform: scaleX(0);
}

The trick is to bind the transition to a class, and when you want to reset it you just remove the class (reset the transition to the initial status) and add it again (restart it).

But there are a few gotchas: the most important is that instantly removing and adding the class will be optimized by the browser, which will just merge the actions and no transition at all will happen. The trick is to wrap the calls in a nested rAF call, which will force the browser to execute, render, and then execute again.

window.requestAnimationFrame(function() {
  document.getElementById("countdown-fill").classList.remove('reset');   
  window.requestAnimationFrame(function() {
    document.getElementById("countdown-fill").classList.add('reset');
  });
});

The second is related to transitions: to optimize browser rendering, avoid transitioning properties like width or height, and try to limit to transforms and opacity. I've changed your width transition into a transform transition: same effect, more performance.

Upvotes: 7

Related Questions