13

I have an element which is randomly animated with CSS and JS with the help of CSS custom properties in the following way:

var myElement = document.querySelector('#my-element');

function setProperty(number) {
  myElement.style.setProperty('--animation-name', 'vibrate-' + number);
}

function changeAnimation() {
  var number = Math.floor(Math.random() * 3) + 1;
  setProperty(number);
  /* restart the animation */
  var clone = myElement.cloneNode(true);
  myElement.parentNode.replaceChild(clone, myElement);
}

myElement.addEventListener('animationend', changeAnimation, false);
#my-element {
  width: 50px;
  height: 50px;
  background: #333;
}

 :root {
  --animation-name: vibrate-1;
}

#my-element {
  animation: 3.3s 1 alternate var(--animation-name);
}

@keyframes vibrate-1 {
  0% {
    opacity: 0;
    transform: scale(0.95);
  }
  50% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(0.9);
  }
}

@keyframes vibrate-2 {
  0% {
    opacity: 0;
    transform: scale(0.5);
  }
  50% {
    opacity: 1;
    transform: scale(0.9);
  }
  100% {
    opacity: 0;
    transform: scale(0.5);
  }
}

@keyframes vibrate-3 {
  0% {
    opacity: 0;
    transform: scale(0.3);
  }
  50% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(0.8);
  }
}
<div id="my-element"></div>

The idea behind is to have a set of animations which switch on each animation’s end randomly to another one. (that for the opacity in the end is always 0 to make a smooth invisible switch.)

Now, surprisingly, this code above runs just fine, except that it does only once and then stop.

I now there are JS loop techniques but I have no idea how to exactly implement them inside this workflow.

Can someone help me?

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Garavani
  • 755
  • 1
  • 13
  • 28

2 Answers2

8

When you replace the element with the cloned element, you should reassign the animationend event listener:

var clone = myElement.cloneNode(true);
clone.addEventListener('animationend', changeAnimation, false);
myElement.parentNode.replaceChild(clone, myElement);

By the way, variables in JavaScript can't contain -, so my-element should be myElement.

Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
6

Another simple idea is to rely on animationiteration and make the animation to run infinite then you no more need to clone the element. You simply change the animation name each iteration and you will have the needed effect:

var myElement = document.querySelector('#my-element');

function setProperty(number) {
  myElement.style.setProperty('--animation-name', 'vibrate-' + number);
}

function changeAnimation() {
  var number = Math.floor(Math.random() * 3) + 1;
  setProperty(number);
}

myElement.addEventListener('animationiteration', changeAnimation, false);
#my-element {
  width: 50px;
  height: 50px;
  background: #333;
}

#my-element {
  animation: 3.3s alternate infinite var(--animation-name,vibrate-1);
}

@keyframes vibrate-1 {
  0% {
    opacity: 0;
    transform: scale(0.95);
  }
  50% {
    opacity: 1;
    transform: scale(1);
    background:green;
  }
  100% {
    opacity: 0;
    transform: scale(0.9);
  }
}

@keyframes vibrate-2 {
  0% {
    opacity: 0;
    transform: scale(0.5);
  }
  50% {
    opacity: 1;
    transform: scale(0.9);
    background:red;
  }
  100% {
    opacity: 0;
    transform: scale(0.5);
  }
}

@keyframes vibrate-3 {
  0% {
    opacity: 0;
    transform: scale(0.3);
  }
  50% {
    opacity: 1;
    transform: scale(1);
    background:blue;
  }
  100% {
    opacity: 0;
    transform: scale(0.8);
  }
}
<div id="my-element"></div>

Another way is to simply keep one animation and adjust the scale values (or any other values) and you will have a better random behavior.

var myElement = document.querySelector('#my-element');

function changeAnimation() {
  var n1 = Math.random();
  myElement.style.setProperty('--s1',n1);
  var n2 = Math.random();
  myElement.style.setProperty('--s2',n2);
  var c1 = Math.floor(Math.random()*255);
  myElement.style.setProperty('--c1',c1);
  var c2 = Math.floor(Math.random()*255);
  myElement.style.setProperty('--c2',c2);
}

myElement.addEventListener('animationiteration', changeAnimation, false);
#my-element {
  width: 50px;
  height: 50px;
  background: #333;
}

#my-element {
  animation: 3.3s alternate infinite vibrate;
}

@keyframes vibrate {
  0% {
    opacity: 0;
    transform: scale(var(--s1,0.95));
  }
  50% {
    opacity: 1;
    transform: scale(1);
    background:rgb(255,var(--c1,0),var(--c2,0));
  }
  100% {
    opacity: 0;
    transform: scale(var(--s2,0.9));
  }
}
<div id="my-element"></div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • 1
    Wow! In fact, the code above didn’t really work, at all. After the second run I was struggling with this Error: TypeError: null is not an object (evaluating 'myElement.parentNode.replaceChild') probably because the original element is no longer existent. Your code works much better! – Garavani Feb 01 '19 at 10:19
  • I discovered that in the second solution the variables are not really inserted into the keyframe values. The animation just uses the defaults. They do not seem to be supposed to be dynamic as I read here: https://stackoverflow.com/questions/18481550/how-to-dynamically-create-keyframe-css-animations (first answer) – Garavani Feb 02 '19 at 09:21
  • @Garavani that answer has nothing to do with CSS variable. And if you check well the animation you see the color changing which means that the value are changing. Here is another example with the same thing working fine : https://stackoverflow.com/a/49750566/8620333 ... it's probably hard to notice the difference for the scale – Temani Afif Feb 02 '19 at 09:36
  • @Garavani or show me the code you have used so I can see the issue – Temani Afif Feb 02 '19 at 09:40
  • 1
    @Garavani no, don't edit the question since actually it's answered and you accepted the answer so you should avoid changing the meaning of your initial issue. Use this : https://jsfiddle.net/ and put your code there and send me the link – Temani Afif Feb 02 '19 at 09:43
  • Here it is: https://jsfiddle.net/yo308L7t/ opacity works, translates don’t. (I also tried many different ways to attach the ‘vw’ unit or used other units etc.) – Garavani Feb 02 '19 at 09:55
  • 1
    @Garavani you need to use `calc` you cannot do muliplication like wihout --> `calc(var(--y0) * 1vw)` – Temani Afif Feb 02 '19 at 10:02
  • @Garavani you are also using very small values, you will notice nothing – Temani Afif Feb 02 '19 at 10:06
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187780/discussion-between-garavani-and-temani-afif). – Garavani Feb 02 '19 at 10:10
  • It is maybe worth noting that code snippet no2 does not work as expected on Safari (12.0.3) – Garavani Feb 02 '19 at 10:33