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:
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.