Animating with Vue

Hello 👋
Ramona Bîscoveanu

👩🏼💻 Senior Developer @ SAP
@CodesOfRa
🧀 ☕️🌱
⚠️ Motion⚠️
Remember the 90s?

Why animate?
It's all about communication

It's also that animations are
FUN 👯♀️

<Transition/>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

- <transition> as a component's root will no longer trigger transitions when the component is toggled from the outside
- v-enter-from/v-leave-from
Vue 3
<TransitionGroup />
- <transition-group> no longer renders a root element by default, but can still create one with the tag attribute.
Vue 3

<transition-group tag="div" class="tile-section" name="list" appear>
<TileComp
v-for="(tile, i) in tiles"
:key="i + 'tile'"
:header="tile.title"
:content="tile.content"
:footer="tile.footer"
></TileComp>
</transition-group>
.list-enter-active,
.list-leave-active {
transition: all 1s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateY(70px);
}
JavaScript Hooks
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>


🎉
🎉
SVG
+
=
🎉
Tween
gsap.to(".selector", {toVars});
gsap.from(".selector", {fromVars});
gsap.fromTo(".selector", {fromVars}, {toVars});
// special properties (duration, ease, etc.) go in toVar
gsap.set(".selector", {toVars});

<div class="tile-section">
<TileComp
v-for="(tile, i) in tiles"
:key="i + 'tile'"
:header="tile.title"
:content="tile.content"
:footer="tile.footer"
></TileComp>
</div>
import gsap from "gsap";
...
mounted() {
gsap.from(".tile", {
duration: 0.5,
opacity: 0,
scale: 0,
y: 200,
ease: "power2",
stagger: 0.1,
});
},

https://twitter.com/hexagoncircle/status/1527715562956066817

<h1 ref="celebrate" class="counter" :class="{ celebrate: isCelebrate }">
{{ this.numberWithCommas(value) }}
</h1>
const tl = gsap.timeline();
mounted() {
tl.fromTo(
".counter",
{
innerText: start,
scale: 0.8,
},
{
innerText: end,
snap: { innerText: 1 },
duration: 4,
ease: "linear",
onUpdate: () => {
this.$refs.celebrate.innerText = this.numberWithCommas(
this.$refs.celebrate.innerText
);
},
onComplete: () => {
this.celebrate();
},
}
).to(".counter", {
scale: 1,
ease: "elastic.out(1, 0.2)",
duration: 1.2,
});
},
restart() {
tl.restart();
this.isCelebrate = false;
}
Page transition
<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>

<router-view v-slot="{ Component }">
<transition
:key="$route.path"
@enter="onEnter"
@leave="onLeave"
:css="false"
>
<component :is="Component" />
</transition>
</router-view>
import gsap from "gsap";
import SplitText from "gsap/SplitText";
gsap.registerPlugin(SplitText);
gsap.defaults({
duration: 1,
ease: "power3.inOut",
});
mySplitText(el) {
return new SplitText(el, { type: "words,chars,lines" });
},
...
onEnter(el, done) {
const masterTL = gsap.timeline({
onComplete: () => {
done;
},
});
masterTL.add(
this.enterContentTextAnimation(
mySplitText(".content-text-header").chars
)
);
masterTL.add(
this.enterContentTextAnimation(".content-text-body"),
"-=0.9" //overlap with previous by 0.9s
);
masterTL.add(this.imgScaleOut(".content img"), "<");
//The start of previous animation
},
enterContentTextAnimation(id) {
const tl = gsap.timeline();
tl.fromTo(
id,
{
yPercent: "100",
opacity: 0,
},
{
yPercent: "0",
opacity: 1,
stagger: 0.03,
}
);
},
....
imgScaleOut(id) {
const tl = gsap.timeline();
tl.from(id, {
scale: 1.5,
});
return tl;
},
SVG

watch: {
data(newValue, oldValue) {
newValue.map((data, index) => {
var id = "#arc" + index;
var d = this.calculateArc(data, index);
var oldValueD = this.calculateArc(oldValue[index]);
const tl = gsap.timeline();
tl.fromTo(
id,
{
attr: { d: oldValueD },
},
{
duration: 0.5,
attr: { d: d },
yoyo: true,
}
);
});
},
},
Thank you!

https://github.com/CodesOfRa/co2-radial-bar-chart
https://github.com/CodesOfRa/vue-animation-counter
Animating with Vue - Fundamental
By Ramona Biscoveanu
Animating with Vue - Fundamental
- 416