csscss-transformsvelocity.js

Using scaleX and translationX at the same time (VelocityJS)


I have a bar that is filled from a certain percentage to another percentage (0% - 50%). I want to smoothly animate this towards a new range (25% - 55%). This means both the width and the location of the bar will need to be changed. I'm having some trouble doing both of these smoothly at the same time.

After some research I found I needed to use scaleX to smoothly animate the width and translateX to smoothly animate the location (where translateX is also affected by the scale). The problem that occurs now is that the bar will overstep it's desired percentage (55%) and then move back as shown in the snippet below.

/* Button function to restart. */
const restart = () => {
  animate();
}

const animate = () => {
  /* Reset the bar to its starting position. (from 0% - 50%) */
  Velocity(document.getElementById('movingBar'), {
    scaleX: 0.5,
    translateX: 0
  },
  {
    duration: 0,
    easing: [0, 0, 1, 1]
  });
  
  /* Move the bar to its final position. (from 25% - 55%). */
  /* Split into two velocity calls so that they can have a seperate duration/easing if needed. */
  Velocity(document.getElementById('movingBar'), {
    scaleX: 0.30
  },
  {
    duration: 1000,
    easing: [0, 0, 1, 1],
    queue: false
  });
  
  Velocity(document.getElementById('movingBar'), {
    translateX: (25 / 0.30) + '%'
  },
  {
    duration: 1000,
    easing: [0, 0, 1, 1],
    queue: false
  });
};

/* Start animation on run. */
animate();
#root {
  width: 700px;
  height: 100%;
}

#container {
  width: 100%;
  height: 90px;
  background-color: #000000;
}

.bar {
  width: 100%;
  height: 30px;
  transform: scaleX(0.5);
  transform-origin: left;
  background-color: #FF0000;
}

#description {
  display: flex;
}

.percentage {
  display: flex;
  justify-content: flex-end;
  width: 10%;
  height: 20px;
  text-align: right;
}

.odd {
  background-color: #DDDDDD;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<div id='root' style='width: 100%; height: 100%'>
  <div id='container'>
    <div class="bar" style="background-color: #00FF00;"></div>
    <div id='movingBar' class="bar"></div>
    <div class="bar" style="background-color: #00FF00; transform: scaleX(0.30) translateX(calc(25 / 0.30 * 1%))"></div>
  </div>
  <div id='description'>
    <div class="percentage even">10%</div>
    <div class="percentage odd">20%</div>
    <div class="percentage even">30%</div>
    <div class="percentage odd">40%</div>
    <div class="percentage even">50%</div>
    <div class="percentage odd">60%</div>
    <div class="percentage even">70%</div>
    <div class="percentage odd">80%</div>
    <div class="percentage even">90%</div>
    <div class="percentage odd">100%</div>
  </div>
  <button onClick="restart()">restart</button>
</div>

The example shows the starting position in the top green bar, and the desired position it should animate to in the bottom green bar. The middle red bar is the bar that should animate from the starting position to the desired position. As you can see the red bar reaches the desired result eventually but not before going over 55% for a bit. I'm using VelocityJS to animate at the moment.

Any idea what I'm doing wrong or how I would go about animating this? Is there some calculation I need to do to the duration/easing to correct whatever is going wrong?


Solution

  • The issue is related to how the interpolation of values is done. It would be a hard work to make sure to have a global linear transformation using scaleX and translateX because you cannot control the interpolation of values and the browser will do this for you. so either you do a complex calculation of the duration/easing to find a perfect result or you consider another kind of animation.

    For such situation I would consider clip-path even if the support isn't optimal but it will be easier to handle since you have only one property to animate and you don't need any complex calculation as you simply have to use the percentage of the graph.

    Here is a simplified example:

    body {
      background:#000;
    }
    .box {
      height:50px;
      background:red;
      clip-path:polygon(10% 100%,10% 0,     40% 0,40% 100%); /*from [10% 40%]*/
      transition:1s all;
    }
    
    body:hover .box {
      clip-path:polygon(50% 100%,50% 0,     60% 0,60% 100%); /*to [50% 60%]*/
    }
    <div class="box">
    
    </div>