0

Please see the demo below, or at https://jsfiddle.net/ut7y93hv/

I am getting a random name out of Peter, Paul, and Mary, and if the name is different from before, then have the "blur out" effect, change the name, and then make it "blur in" (make it sharp again).

So right now I have to do

    setTimeout(() => {
      displayElement.classList.remove("blur-and-back");
    }, 2000);

which is to remove the class name after the animation is done. Otherwise, if I just add the class name next time, it would be as if nothing happened -- it won't animate again. I also double checked: I cannot remove the class name and then immediately add it back, because it also will be as if nothing has happened.

The question is: is there a way to do it more elegantly using CSS animation? Somehow just to tell the animation to restart itself, without needing to remove the class and add the class back? It is also messy when it is done in ReactJS, because we also need to do the setTimeout() to do low level things "under the hood" to make it happen.

I may be able to make it a constantly repeating animation, but "pause" after 2 seconds using a setTimeout(), and then restart it again next time, but it may be worse if the setTimeout() is delayed for 16ms or 33ms, and then the animation is shifted more and more, and out of sync, after some time.

The need to start the animation and then set a timer to alter the text is somewhat messy too. I think I can first blur the text, and use the event animationend to change the text, and kickstart making it sharp again, and use the event animationend to remove the class, but this is also a bit messy.

const names = ["Peter", "Paul", "Mary"];
const displayElement = document.querySelector("#display");

function pickOne(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
}

let name = pickOne(names),
  prevName = name;
  
displayElement.innerHTML = name;

setInterval(() => {
  name = pickOne(names);
  if (name !== prevName) {
    displayElement.classList.add("blur-and-back");

    setTimeout(() => {
      displayElement.innerHTML = name;
    }, 1000);

    setTimeout(() => {
      displayElement.classList.remove("blur-and-back");
    }, 2000);
    
    prevName = name;
  }

}, 3000);
#display {
  font: 36px Arial, sans-serif;
}

.blur-and-back {
  animation: 2s blur_and_back;
}

@keyframes blur_and_back {
  0% {
    filter: blur(0);
  }
  50% {
    filter: blur(0.72em);
  }
  100% {
    filter: blur(0);
  }
}
<div id="display"></div>
nonopolarity
  • 146,324
  • 131
  • 460
  • 740
  • Do you mean like [`animation-iteration-count: infinite`](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-iteration-count)? – Kaiido Feb 15 '21 at 01:54
  • if the name doesn't change, it shouldn't have the animation effect. and "I may be able to make it a constantly repeating animation, but "pause" after 2 seconds using a setTimeout(), and then restart it again next time, but it may be worse if the setTimeout() is delayed for 16ms or 33ms, and then the animation is shifted more and more, and out of sync, after some time." – nonopolarity Feb 15 '21 at 01:55
  • Ok, I think I see better the case now. – Kaiido Feb 15 '21 at 01:58
  • `animation` is meant to use once or continuous animation, we do have our own style of doing things, but I do recommend to use `transition` for flexibility of animating it in specific events and time. – Shiz Feb 15 '21 at 02:12
  • @Shiz here they need to go forth and back, unfortunately transitions don't do this well. – Kaiido Feb 15 '21 at 02:14
  • @Shiz who ever said animation is meant to use once or continuous animation? – nonopolarity Feb 15 '21 at 02:43

2 Answers2

2

No, you don't need to remove it at that time, you can simply remove it just before setting it on again and trigger a reflow in-between:

const names = ["Peter", "Paul", "Mary"];
const displayElement = document.querySelector("#display");

function pickOne(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
}

let name = pickOne(names),
  prevName = name;

displayElement.innerHTML = name;

setInterval(() => {
  name = pickOne(names);
  if (name !== prevName) {
    displayElement.classList.remove("blur-and-back");
    displayElement.offsetWidth; // trigger reflow
    displayElement.classList.add("blur-and-back");

    setTimeout(() => {
      displayElement.innerHTML = name;
    }, 1000);
    prevName = name;
  }

}, 3000);
#display {
  font: 36px Arial, sans-serif;
}

.blur-and-back {
  animation: 2s blur_and_back;
}

@keyframes blur_and_back {
  0% {
    filter: blur(0);
  }
  50% {
    filter: blur(0.72em);
  }
  100% {
    filter: blur(0);
  }
}
<div id="display"></div>

Note that you could also have a look at the Web Animations API, which offer means to control your animations programmatically.

const names = ["Peter", "Paul", "Mary"];
const displayElement = document.querySelector("#display");

const keyframes = [
  { filter: "blur(0)" },
  { filter: "blur(0.72em)" },
  { filter: "blur(0)" }
];
const duration = 2000;
function pickOne(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
}

let name = pickOne(names),
  prevName = name;

displayElement.innerHTML = name;

setInterval(() => {
  name = pickOne(names);
  if (name !== prevName) {
    displayElement.animate( keyframes, { duration } );
    setTimeout(() => {
      displayElement.innerHTML = name;
    }, 1000);
    prevName = name;
  }

}, 3000);
#display {
  font: 36px Arial, sans-serif;
}
<div id="display"></div>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • It worked in Chrome, Firefox, and Safari... does this method work in most browser? I do not see in the MDN docs about getting the `offsetWidth` can trigger a reflow and therefore let it re-animate. (by the way, won't triggering a reflow be costly if the page is very complicated? – nonopolarity Feb 15 '21 at 02:09
  • if we use `Animation.play()` to restart it, the question is, how do we get the animation object? – nonopolarity Feb 15 '21 at 02:10
  • Triggering a reflow will work in every browser supporting CSS animations (at least back to IE9). Yes it can be costly in badly written browsers, but here there isn't much to recalculate since there is no real change, modern browsers won't tick for that. And you can access CSS Animations through `document.getAnimations()`, but in your case using `element.animate` seems actually a better choice, I did edit accordingly. – Kaiido Feb 15 '21 at 02:13
  • interesting... `displayElement.animate( keyframes, { duration } )` looks like the jQuery way of `el.fadeOut().fadeIn()` No need to use CSS... maybe it is a good idea. And just reading `offsetWidth` would cause a reflow... is it in any specs at all? – nonopolarity Feb 15 '21 at 02:16
  • No need to put it in any specs, reflow is an implementation detail, specs don't spec optimizations. But to be able to return an up to date `offsetWidth` value, the recalc must be done. – Kaiido Feb 15 '21 at 02:20
  • hm... the recalc can be done, but it doesn't have to reflow the elements... but I just read https://stackoverflow.com/questions/27637184/what-is-dom-reflow and https://gist.github.com/paulirish/5d52fb081b3570c81e3a However, if we remove the class, take the measurement, and it reflows right there? It won't wait until we add back the class, reflow, and won't do anything because the class was there before and after the reflow. So it does look like it reflow as soon as `offsetWidth` is accessed, because adding back the class. – nonopolarity Feb 15 '21 at 02:42
  • Not because adding back the class no, just because we accessed the value and did remove the class. If you will it's using a "is_dirty" flag system, when removing the class the is_dirty is raised, normally modern browsers will wait until the next painting frame to perform the recalc and reflow if the flag is up, but since we do ask for a computed value, they have to perform it now (they'll do it again at paiting since we did dirty again the boxes by adding the class again). – Kaiido Feb 15 '21 at 02:48
  • You can have a read at my answers [here](https://stackoverflow.com/questions/47342730/47343090#47343090) and [there](https://stackoverflow.com/questions/54344996/css-transition-doesnt-work-if-element-start-hidden/54348963#54348963) on this subject matter. – Kaiido Feb 15 '21 at 02:49
  • by the way, seems like the `el.animate()` is less supported than CSS animation https://caniuse.com/web-animation – nonopolarity Feb 15 '21 at 02:55
  • Yes of course, it's a quite recent API, but you never said you need to support IE and seeing your use of ES6 that wasn't obvious. But anyway, you've got both solutions, your call. – Kaiido Feb 15 '21 at 03:02
  • yeah, that actually is quite good. IE I am not sure, if people can download Edge and use it instead – nonopolarity Feb 15 '21 at 03:17
  • Your solution works even for https://jsfiddle.net/af4rs3u1/ for transition, so when that console.log is removed for offsetHeight, then it won't work. But if using innerHTML to add the element, then it doesn't work: https://jsfiddle.net/e1wonhaL/ – nonopolarity Feb 15 '21 at 04:18
0

Just like what I commented, animation is meant to use once or continuous animation, we do have our own style of doing things, but I do recommend to use transition for flexibility of animating it in specific events and time.

Since your animation is set to 2 seconds, you can setup your css transition like this:

#display {
  font: 36px Arial, sans-serif;
  transition: filter 650ms;
}

then harnessing Javascript:

function blur_and_back(){
   displayElement.style.filter = "filter:blur(0.72em)";
   setTimeout(() => { displayElement.style.filter = "filter:blur(0)"},650);
}

just call blur_and_back() everytime you want to animate it.

EDIT:

Based on developer.mozilla.org, one component of animation is that animation is:

a style describing the CSS animation and a set of keyframes that indicate the start and end states of the animation’s style

by means of start and end or from and to or 0% and 100% is literally a one time use of animation, or use infinite to continuously animate it.

They also added that:

you can create them without even having to know JavaScript.

meanwhile the transition based on developer.mozilla.org again:

Instead of having property changes take effect immediately, you can cause the changes in a property to take place over a period of time.

CSS transitions let you decide which properties to animate (by listing them explicitly), when the animation will start (by setting a delay), how long the transition will last (by setting a duration), and how the transition will run (by defining a timing function, e.g. linearly or quick at the beginning, slow at the end).

This is what I mean by flexibility.

I guess it's kinda obvious, even for their literal English definition.

Yet, I'll say it again, we do have our own style of doing things, and this is just a recommendation.

Shiz
  • 695
  • 10
  • 27
  • who ever said animation is meant to use once or continuous animation? – nonopolarity Feb 15 '21 at 02:44
  • actually, using transition is considered old style... people would use animation nowadays because it is more advanced. Think about if you have 5 steps of animations (0%, 20%, 40%, 60%, ...), then what do you do? use 5 separate transition and setTimeout to make it happen? – nonopolarity Feb 15 '21 at 03:19
  • most css users usually do both. as long as transition is more effective, I ignore how old it is. but to be honest, I often use animation more, I just use transition if the setup is a bit complicated. to answer your question, I'd rather use animation, but with that setup of removing class and adding it again? I'd use transition. but yet again n again, we do have our own style of doing things. – Shiz Feb 15 '21 at 03:27