Search code examples
javascriptaframeanime.js

For A-Frame (and anime.js), how do I change the animation speed mid-animation?


I am working on an example using A-Frame. The scene consists of a floating object (that bounces between two invisible boundaries). This is achieved using an animation mixin as below:

<a-mixin id="bounceX"
   animation__bouncex="property:object3D.position.x;  to:5; dir:normal;
                        loop:1; dur:3600; easing:linear;
                        startEvents:bouncex;pauseEvents:holdx;resumeEvents:bounceOnx"
></a-mixin>

I reverse the direction by listening to the animationcomplete event and setting:

mixin.data.to *= -1;
el.emit(...)

Now, for interactions with the floating object, when the mouse-cursor hovers over the object, it should slow down, if the user clicks it, it should stop. If the mouse-cursor leaves the object without a click it should resume moving at its original speed. I am able to capture the mouseenter, click and mouseleave events and log them to console trivially.

The problem part of the code is: How to update the animation mid-flight to slow it down? I tried updating the values (duration,dur) in el.components.animation__bouncex.config and el.components.animation__bouncex.data without luck. This doesn't work, the speed doesn't change till the animation completes.

The AFrame>components>animation.js mentions the following:

* The component manually controls the tick by setting `autoplay: false` on anime.js and
 * manually * calling `animation.tick()` in the tick handler. To pause or resume, we toggle a
 * boolean * flag * `isAnimationPlaying`.

Glitch snippet here : https://glitch.com/edit/#!/loud-quilt-marsupial


Solution

  • You can use my animation-speed component (example) which allows you to provide a speed factor:

    // speed up <a-box animation="" by 2x
    element.setAttribute("animation-speed", "multiplier", 2);
    

    Since anime.js is updated with the 'time passed since last renderloop', the component just adds a multiplier.


    If it's a simple animation, You can create a custom component. In the tick(t, dt) function You can write a simple motion equation:

    // called on each render loop
    tick: function(t, dt) {
      // grab the underlying three.js objects position
      const pos = this.el.object3D.position
      
      // position = speed * time
      pos.x = this.speed * dt; // this.speed can be set anywhere in the component
    }  
    

    If we add some boundaries for the object to 'bounce' and change the speed, we end up with something like this:

    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
    <script>
      // component declaration
      AFRAME.registerComponent("foo", {
        init: function() {
          this.xMax = 4;  // x upper boundary
          this.xMin = -4; // x lower boundary
          this.speed = 0.005; // base movement speed
          this.dir = 1;   // animation direction 
          
          // hovering over the sphere will change the animation speed 
          const sphere = document.querySelector("a-sphere")
          this.speedUp = this.changeSpeed.bind(this, 0.01);
          this.slowDown = this.changeSpeed.bind(this, 0.005);
          sphere.addEventListener("mouseenter", this.speedUp) // hover
          sphere.addEventListener("mouseleave", this.slowDown) // leave
        },
        changeSpeed(spd) {
          this.speed = spd;
        },
        tick: function(t, dt) {
          // grab the underlying THREE.js position
          const pos = this.el.object3D.position
          // check if the object has passed the boundaries - if so, change the direction
          if (pos.x > this.xMax) this.dir = -1;
          if (pos.x < this.xMin) this.dir = 1;
           
          // update the position using the direction and speed
          pos.x += this.dir * this.speed * dt;
        }
      })
    </script>
    <a-scene cursor="rayOrigin: mouse" raycaster="objects: a-sphere">
      <a-box foo position="-2 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
      <a-text color="black" position="-1.75 2 -3" value="hover to speed up"></a-text>
      <a-sphere position="-1 1.5 -3" radius="0.25" color="#EF2D5E"></a-sphere>
    </a-scene>