Menu Close

Vue.js Transition Effects — State Transitions

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to create state transitions with GreenSock, tween.js, and Color.js

Animating State with Watchers

Watchers let us animate changes of any numerical property into another property.

For example, we can animate changes for a number that we input as follows with GreenSock and Vue:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    num: 0,  
    tweenedNumber: 0  
  },  
  computed: {  
    animatedNumber() {  
      return this.tweenedNumber.toFixed(0);  
    }  
  },  
  watch: {  
    num(newValue) {  
      TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <input v-model.number="num" type="number" />  
      <p>{{ animatedNumber }}</p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code will above will animate the number changes as we enter a number into the input.

We watch the num value and then called:

TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });

to update the number display by incrementing or decrementing the number until it reaches the number we typed in.

TweenLite is from GreenSock.

We can also do the same thing for other things like a color string.

For example, we can write the following to animate the update of a color string:

src/index.js :

const Color = net.brehaut.Color;new Vue({  
  el: "#app",  
  data: {  
    colorQuery: "",  
    color: {  
      red: 0,  
      green: 0,  
      blue: 0,  
      alpha: 1  
    },  
    tweenedColor: {}  
  },  
  created() {  
    this.tweenedColor = Object.assign({}, this.color);  
  },  
  watch: {  
    color() {  
      const animate = () => {  
        if (TWEEN.update()) {  
          requestAnimationFrame(animate);  
        }  
      };
      new TWEEN.Tween(this.tweenedColor).to(this.color, 750).start();
      animate();  
    }  
  },  
  computed: {  
    tweenedCSSColor() {  
      return new Color({  
        red: this.tweenedColor.red,  
        green: this.tweenedColor.green,  
        blue: this.tweenedColor.blue,  
        alpha: this.tweenedColor.alpha  
      }).toCSS();  
    }  
  },  
  methods: {  
    updateColor() {  
      this.color = new Color(this.colorQuery).toRGB();  
      this.colorQuery = "";  
    }  
  }  
});

src/styles.css :

.color-preview {  
  width: 50px;  
  height: 50px;  
}

index.js :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdn.jsdelivr.net/npm/tween.js@16.3.4"></script>  
    <script src="https://cdn.jsdelivr.net/npm/color-js@1.0.3"></script>  
    <link href="./src/styles.css" type="text/css" />  
  </head>  
  <body>  
    <div id="app">  
      <input  
        v-model="colorQuery"  
        @keyup.enter="updateColor"  
        placeholder="Enter Color"  
      />  
      <button @click="updateColor">Update</button>  
      <div  
        class="color-preview"  
        :style="{ backgroundColor: tweenedCSSColor }"  
      ></div>  
      <p>{{ tweenedCSSColor }}</p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code works above by getting the colorQuery string that was inputted, converting it to a Color object and assign it to this.color .

Once the this.color updates, The animate function in the color watcher in the watch property will be called.

Then this.tweenedColor will be updated with:

new TWEEN.Tween(this.tweenedColor).to(this.color, 750).start();

Then once this.tweenedColor is updated, then tweenedCSSColor is updated and displayed on the screen.

TWEEN.Tween is a constructor from tween.js.

Organizing Transitions into Components

We can put our transition code into its component. This way, we can reuse it and separate out the complexity of the transitions from other business logic.

For example, we can refactor our original example into the following code:

src/index.js :

Vue.component("num-transition", {  
  props: ["num"],  
  data() {  
    return {  
      tweenedNumber: 0  
    };  
  },  
  computed: {  
    animatedNumber() {  
      return this.tweenedNumber.toFixed(0);  
    }  
  },  
  watch: {  
    num(newValue) {  
      TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });  
    }  
  },  
  template: `<p>{{animatedNumber}}</p>`  
});new Vue({  
  el: "#app",  
  data: {  
    num: 0  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <input v-model.number="num" type="number" />  
      <num-transition :num="num"></num-transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we moved the transition logic into its own component by moving out all the transition logic and then passing the number that we want to watch for creating the transition as a prop.

Then we can change it slightly so that we can reuse it as follows:

src/index.js :

Vue.component("num-transition", {  
  props: ["num"],  
  data() {  
    return {  
      tweenedNumber: 0  
    };  
  },  
  computed: {  
    animatedNumber() {  
      return this.tweenedNumber.toFixed(0);  
    }  
  },  
  watch: {  
    num(newValue) {  
      TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });  
    }  
  },  
  template: `<span>{{animatedNumber}}</span>`  
});new Vue({  
  el: "#app",  
  data: {  
    num1: 0,  
    num2: 0  
  },  
  computed: {  
    result() {  
      return this.num1 + this.num2;  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src=https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <input v-model.number="num1" type="number" />  
      <input v-model.number="num2" type="number" />  
      <p>  
        <num-transition :num="num1"></num-transition> +  
        <num-transition :num="num2"></num-transition> =  
        <num-transition :num="result"></num-transition>  
      </p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we changed the template of num-transition to:

<span>{{animatedNumber}}</span>

and then in index.html we changed the code to:

<div id="app">  
  <input v-model.number="num1" type="number" />  
  <input v-model.number="num2" type="number" />  
  <p>  
    <num-transition :num="num1"></num-transition> +  
    <num-transition :num="num2"></num-transition> =  
    <num-transition :num="result"></num-transition>  
  </p>  
</div>

Then we get:

https://thewebdev.info/wp-content/uploads/2020/04/calc.png

Conclusion

We can create transitions for component states by watching them and creating computed values for them.

Then we can use the GreenSock library to generate new values from the watched value.

Then that value can be displayed on the screen, creating an animation effect.

We can refactor the logic into its own component so that it won’t make a component too complex by mixing transition logic and other business logic.

Posted in vue