2

How can I detect changes made by CSS3 animations or web animations (Element.animate)??

(Sorry for my bad English! this is my first question in Stackoverflow)

I know about MutationObserver, it responds only if I change the inline style or if I use requestAnimationFrame (since I change the inline style using it). But if I use CSS3 animations or Web animations, MutationObserver doesn't respond since they don't change the inline style.

See this... There are two divs here... div1, div2. div1's position will change when div2's position changes. But this happens only if I use requestAnimationFrame as I said before.

My question is how can I do this for css3 animations and web animations (Element.animate)?

const div1 = document.getElementById('div1');
const div2 = document.getElementById('div2');

/***** Add mutation observer to detect change *****/

const mutation = new MutationObserver(mutations => {
  div1.style.left = div2.style.left;
});

mutation.observe(div2, {
  attributes: true
});

/***** Animation with css *****/

function cssAnimation() {
  div2.style.animation = 'anim 1.5s linear';
}

/***** Animation with web animations *****/

function webAnimation() {
  div2.animate({
    left: [0, '500px']
  }, {
    duration: 1500,
    easing: 'linear'
  });
}

/*****Animation with requestAnimationFrame ******/

// Current left position of div2
const left = 0;

function requestAnimation() {
  // Increase left position 5px per keyframe
  div2.style.left = `${(left += 5)}px`;

  // Increase left position until it reaches to 500px
  if (left < 500) {
    requestAnimationFrame(requestAnimation);
  }
}

function clearAnimations() {
  left = 0;
  div2.style.left = 0;
  div2.style.animation = 'unset';
}
@keyframes anim {
  from {
    left: 0;
  }
  to {
    left: 500px;
  }
}

#div1 {
  background: orange;
  width: 100px;
  height: 100px;
  position: absolute;
  top: 200px;
}

#div2 {
  background: lightgreen;
  width: 100px;
  height: 100px;
  position: absolute;
  top: 100px;
}
<div id="buttons">
  <h3>Animate with...</h3>
  <button onclick='cssAnimation()'>Css3</button>
  <button onclick="requestAnimation()">request animation frame</button>
  <button onclick="webAnimation()">web animations api</button>
  <button id="clear" onclick="clearAnimations()">Clear</button>
</div>

<div id="div1">
  Div1
</div>
<div id="div2">
  div2
</div>
  • When do you want to detect the changes to CSS? – guest271314 Oct 22 '17 at 04:49
  • I know I can use animationstart and update that by setInterval until animationend event occurs when animationend event occur then clearInterval. But it has performance issue. – Ibrahim Al Khalil Oct 22 '17 at 04:51
  • @IbrahimAlKhalil why would you use setInterval? There's absolutely no need. Please read up on css animations and classes. You are approaching this completely wrong. – Darkrum Oct 22 '17 at 04:55
  • @guest271314 I want to detect changes during animation. – Ibrahim Al Khalil Oct 22 '17 at 05:00
  • @IbrahimAlKhalil the Web Animations API is just a wrapper around manipulating the underling animation and transition css "a nicer interface instead of directly writing the css" so the standard listeners for both animation and transitions can be used. But I'm going to tell you now this spec is DOA http://caniuse.com/#feat=web-animation don't use it. – Darkrum Oct 22 '17 at 05:12
  • @Darkrum Thanks bro! there is a polyfill maybe you know that. https://github.com/web-animations/web-animations-js. – Ibrahim Al Khalil Oct 22 '17 at 05:17
  • @Darkrum What do you mean by "this spec is DOA"? What is the issue with using Web Animations API? – guest271314 Oct 22 '17 at 05:17
  • @IbrahimAlKhalil You can use `window.getComputedStyle()`, see Answer – guest271314 Oct 22 '17 at 05:18

4 Answers4

1

Both CSS Animations and Web Animations are based on the principle that you delegate the playback of the animation to the browser. That allows the browser to run the animation on a separate thread or process when possible, so that it runs smoothly. Updating style from JavaScript on each frame is best avoided where possible.

In your example, can you simply run the animation on both elements at the same time? Web Animations, at least, allows synchronizing the animations.

When the remainder of the Web Animations API is shipped, it will be much easier to duplicate animations from one element and apply them to another but for now you would need to call animate twice.

As others have pointed out, it is possible to observe significant moments in the playback of animations (when they start, finish, repeat etc.) but there is no event that runs on each frame. If you want to perform and action on each frame you need to use requestAnimationFrame.

If you pass div2 to getComputedStyle in requestAnimationFrame you will get the animated style for that frame which you can then apply to div1. (That is, reading div2.style.left will only give you the style specified via the style attribute but getComputedStyle(div2).left will give you the animated style including style changes from CSS animations and Web Animations). But, again, that will lead to poor performance and the two animations will not necessarily be synchronized since the CSS animation or Web animation may run on a different thread or process.

brianskold
  • 809
  • 5
  • 10
0

For css animations, you have the animationstart and animationend events that could help you with what you are trying to achieve. However, there is no animationchange or animationupdate event, and it is this way, as far as I know, by design. Without events during the animation happening, it is possible to reach full hardware acceleration, with the interpolation computations done directly in the GPU. So, be aware that while you might be able to mimic what an animationchange event would do via animationstart, animationend and requestAnimationFrame, this is probably going to involve a performance penalty.

0

You're over complicating things. But to listen for animation changes you can listen on these events instead of the mutation observer.

animationstart //will fire has soon as the animation starts
animationiteration //will fire if the same animation is looped can be infinity or more then 2
animationend // will fire when the animations have ended

also please use translate instead of animating the left property.

Darkrum
  • 1,325
  • 1
  • 10
  • 27
0

You can use requestAnimationFrame and window.getComputedStyle() to get current animated styles during the animation, note, included fill:"forward" at Element.animate() call

var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2");

/***** Add mutation observer to detect change *****/

var mutation = new MutationObserver(function(mutations) {
  div1.style.left = div2.style.left;
});
mutation.observe(div2, {
  attributes: true
});

/***** Animation with css *****/

function cssAnimation() {
  div2.style.animation = "anim 1.5s linear forwards";
  let animationFrame;

  function getCurrentStyles() {
    console.log(window.getComputedStyle(div2).left);
    animationFrame = requestAnimationFrame(getCurrentStyles)
  }
  getCurrentStyles();
  div2.addEventListener("animationend", () => cancelAnimationFrame(animationFrame));
}

/***** Animation with web animations *****/

function webAnimation() {
  let animationFrame;

  function getCurrentStyles() {
    console.log(window.getComputedStyle(div2).left);
    animationFrame = requestAnimationFrame(getCurrentStyles)
  }
  getCurrentStyles();
  div2.animate({
    left: [0, "500px"]
  }, {
    duration: 1500,
    fill: "forwards",
    easing: "linear"
  }).onfinish = function() {
    cancelAnimationFrame(animationFrame);
    console.log(window.getComputedStyle(div2).left);
  };
}

/*****Animation with requestAnimationFrame ******/

//Current left position of div2
var left = 0;

function requestAnimation() {
  //Increase left position 5px per keyframe
  div2.style.left = `${(left += 5)}px`;
  console.log(window.getComputedStyle(div2).left);
  //Increase left position until it reaches to 500px
  if (left < 500) {
    requestAnimationFrame(requestAnimation);
  }
}

function clearAnimations() {
  left = 0;
  div2.style.left = 0;
  div2.style.animation = "unset";
}
@keyframes anim {
  from {
    left: 0;
  }
  to {
    left: 500px;
  }
}

#div1 {
  background: orange;
  width: 100px;
  height: 100px;
  position: absolute;
  top: 200px;
}

#div2 {
  background: lightgreen;
  width: 100px;
  height: 100px;
  position: absolute;
  top: 100px;
}
<div id="buttons">
  <h3>Animate with...</h3>
  <button onclick='cssAnimation()'>Css3</button>
  <button onclick="requestAnimation()">request animation frame</button>
  <button onclick="webAnimation()">web animations api</button>
  <button id="clear" onclick="clearAnimations()">Clear</button>
</div>

<div id="div1">
  Div1
</div>
<div id="div2">
  div2
</div>
guest271314
  • 1
  • 15
  • 104
  • 177
  • Thanks bro, you have to use onfinish manually with every animate method, how can you know that an element is going to animate using animate method. – Ibrahim Al Khalil Oct 22 '17 at 05:27
  • _"how can you know that an element is going to animate using animate method."_ Because that is what your code does at that code block. Included same approach for CSS animations and animations using `requestAnimationFrame()`. Are you trying to present a hypothetical Question, where the type of animation implementation is not known? Or are you actually trying to find a solution given the code at Question? – guest271314 Oct 22 '17 at 05:31
  • see https://atomiks.github.io/tippyjs/ there is sticky tooltip if you change translate or left/right property of the target element by animate the tooltip will follow the target and reposition. – Ibrahim Al Khalil Oct 22 '17 at 05:37
  • @IbrahimAlKhalil How is the link and description of the functionality of the code at repository related to the current Question? – guest271314 Oct 22 '17 at 05:40
  • I'm not trying to present a hypothetical Question, and I'm not trying to find a solution given the code at my Question? – Ibrahim Al Khalil Oct 22 '17 at 05:42
  • @IbrahimAlKhalil The code at Answer meets requirement described at original Question _"My question is how can I do this for css3 animations and web animations (Element.animate)?"_ by getting the styles of the element being animated by CSS, `Element.animate()` and `requestAnimationFrame()` using `window.getComputedStyle()` and `requestAnimationFrame()` during the animation. Have you tried the code at stacksnippets at Answer? – guest271314 Oct 22 '17 at 05:45
  • I think you do not understand what I'm looking for, and I can't meant you. because My English is bad. your answer is correct, but I'm not looking for that. – Ibrahim Al Khalil Oct 22 '17 at 05:51
  • @IbrahimAlKhalil Estimation of language usage being "bad" has nothing to do with the Question. The specific question is _"My question is how can I do this for css3 animations and web animations (Element.animate)?"_ which the code at Answer achieves. Are you trying to determine if there is an existing event or events which do exactly what you are trying to do? What is the requirement? See https://stackoverflow.com/help/how-to-ask – guest271314 Oct 22 '17 at 05:52
  • I'm creating a library for tooltip and popover and something like that, when the target element changes position the tooltip or popover doesn't follow the target and this is my problem – Ibrahim Al Khalil Oct 22 '17 at 06:01
  • @IbrahimAlKhalil Have you considered including your actual code and description of what you are trying to achieve at the Question itself? _"I'm creating a library for tooltip and popover and something like that, when the target element changes position the tooltip or popover doesn't follow the target and this is my problem"_ does not appear at original Question, see https://stackoverflow.com/help/mcve – guest271314 Oct 22 '17 at 06:02
  • @IbrahimAlKhalil You managed to create and include code and a question at OP that is not what your actual code is, and not what you are actually trying to achieve. You should be able to include the full code that you are inquiring about and specific requirement at the text of the Question; that is, if the actual question is not _"My question is how can I do this for css3 animations and web animations (Element.animate)?"_ – guest271314 Oct 22 '17 at 06:05
  • @IbrahimAlKhalil _"I'm creating a library for tooltip and popover and something like that, when the target element changes position the tooltip or popover doesn't follow the target and this is my problem"_ If you are trying to observe an element position in relation to a different element position, you can try using `IntersectionObserver()`, see [How to know scroll to element is done in Javascript?](https://stackoverflow.com/q/46795955/) – guest271314 Oct 22 '17 at 06:13
  • Wait bro, I'm creating a pen on codepen then you will understand what I'm looking for. – Ibrahim Al Khalil Oct 22 '17 at 06:20