How to detect changes made by CSS3 animation or we

2019-08-02 02:38发布

问题:

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 inline style or if I use requestAnimationFrame (since I change inline style using it). But if I use css3 animations or Web animations, MutationObserver doesn't respond since they doesn't change 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)?

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";
}

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

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

/*****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`;

  //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>

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

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>