Implementing motion using Vue
Gerard Sans | @gerardsans

SANS
GERARD
Google Developer Expert

Google Developer Expert
International Speaker

Spoken at 97 events in 26 countries

Blogger
Blogger
Community Leader

900
1.5K
Trainer

Master of Ceremonies

Master of Ceremonies











Why introduce motion?
Improved UX ✨

Perceived Performance


Immersive interactions

Better engagement

User happiness 😃

Animation Principles

Squash & Stretch + Anticipation

Follow through

Exaggeration

Composition

Motion Design
 Principles
Easing


Offset Delay

Parenting

Binding Values

Overlay

CSS Transitions

DURATION
opacity: 0;
opacity: 1;
CSS Transitions
PROPERTY
transition: opacity 1s ease-in 1s;
DURATION
TIMING
DELAY






timing function/easing
Fade
<div class="container">
<div class="circle base elastic"></div>
</div>
.circle {
border-radius: 50%;
position: absolute; opacity:0;
will-change: opacity, transform;
}
.base {
transition: opacity 1s ease,
transform 1s ease;
}
.elastic {
transition-timing-function:
cubic-bezier(.8,-0.6,0.2,1.5);
}
.container:hover .circle {
transform: translateX(80px);
opacity:1;
}
Fade
Animatable CSS
all background* border* bottom box-shadow clip clip-path color filter font* height left margin* mask* offset* opacity outline* padding* perspective* right text-decoration text-shadow top transform vertical-align visibility width z-index
CSS Animations









DURATION
CSS Animations
0%
100%
KEYFRAMES
NAME
animation:  fade 1s 1s infinite linear;
DURATION
DELAY
ITERATIONS
TIMING
Fade

<div class="circle base elastic"></div>
.base {
animation: fade 2s infinite;
}
@keyframes fade {
0% {
transform: translateX(0px);
opacity: 0;
}
40%, 60% {
transform: translateX(80px);
opacity: 1;
}
100% {
transform: translateX(0px);
opacity: 0;
}
}
Animations Performance
60 FPS
Smooth 60FPS
60 FPS
Time
16.66ms
16.66ms
16.66ms
1000/60ms
paint
frame
paint
frame
paint
frame
😃
paint
frame
paint
frame
Losing Frames!
60 FPS
Time
16.66ms
16.66ms
16.66ms
😱
Motion Techniques
Move
transform: translateX(10px);
Resize
transform: scale(0.5);
Rotate
transform: rotate(1turn);
Fade
opacity: 0;




Move

<div class="circle base elastic"></div>
.base {
animation: move 2s infinite;
}
.elastic {
animation-timing-function:
cubic-bezier(.8,-0.6,0.2,1.5);
}
@keyframes move {
0% {
transform: translateX(-250px);
}
40%, 60% {
transform: translateX(50px);
}
100% {
transform: translateX(250px);
}
}
Rotate

<div class="square base linear"></div>
.base {
animation: spin 2s infinite;
}
.linear {
animation-timing-function: linear;
}
@keyframes spin {
to {
transform: rotate(1turn);
}
}
Resize
<div class="circle base"></div>
.base {
animation: size 2s infinite;
}
@keyframes size {
0%, 40%, 100% {
transform: scale(1);
}
25%, 60% {
transform: scale(1.1);
}
}

Vue Animations
.fade-in
.fade-out
Fading using CSS classes
opacity: 1
opacity: 0
transition: opacity 1s;
.fade
show/hide
<template>
<div class="box" @click="show=!show">
<div class="circle fade"
:class="show?'fade-in':'fade-out'"></div>
</div>
</template>
<script>
export default {
data() {
return {
show: false
};
} }
</script>
src/app.component.ts
Fading using CSS classes
src/App.vue
<style>
.fade {
transition:
opacity 400ms cubic-bezier(.8,-0.6,0.2,1.5),
transform 400ms cubic-bezier(.8,-0.6,0.2,1.5);
}
.fade-in {
opacity: 1;
transform: translateX(80px);
}
.fade-out {
opacity: 0;
}
</style>
src/app.component.ts
Fading using CSS classes
src/App.vue
- Transition Hooks
- before
- enter/leave
- after
- cancelled
- JS libs
- Transition Classes
- enter/leave
- active
- to
- CSS libs
CSS
JS
Transitions Classes
enter/leave
v-leave
v-leave-active
v-leave-to
v-enter
v-enter-active
v-enter-to
opacity:0
opacity:1
setup
setup
DOM added
DOM removed
raf
raf
opacity:0
opacity:1
<template>
<div class="box" @click="show=!show">
<transition name="fade">
<div class="circle" v-if="show"></div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: false
};
</script>
src/app.component.ts
Using vue CSS transitions
src/App.vue
<style>
.circle {
transform: translateX(80px);
}
.fade-enter, .fade-leave-to {
opacity: 0;
transform: translateX(-80px);
}
.fade-enter-active, .fade-leave-active {
transition: opacity 400ms cubic-bezier(.8,-0.6,0.2,1.5),
transform 400ms cubic-bezier(.8,-0.6,0.2,1.5);
}
.fade-enter-to, .fade-leave {
opacity: 1;
}
</style>
src/app.component.ts
Using vue CSS transitions
src/App.vue
- Transition Hooks
- before
- enter/leave
- after
- cancelled
- JS libs
- Transition Classes
- enter/leave
- active
- to
- CSS libs
CSS
JS
<template>
<div class="box" @click="show=!show">
<transition name="fade" :css="false"
@enter="fadeIn" @leave="fadeOut">
<div ref="circle" class="circle" v-if="show"></div>
</transition>
</div>
</template>
<script>
export default {
methods: {
fadeIn: function(el, done){ ... },
} };
</script>
src/app.component.ts
Using JS hooks
src/App.vue
fadeIn: function(el, done) {
const animation = this.toggle(el, 0, 1);
animation.onfinish = done;
},
toggle: function(el, from, to){
const start = from?80:0;
const end = from?0:80;
return el.animate([
{ opacity: from, transform: `translateX(${start}px)` },
{ opacity: to, transform: `translateX(${end}px)` }
], {
duration: 400, fill: "both",
easing: 'cubic-bezier(.8,-0.6,0.2,1.5)'
});
}
src/app.component.ts
Using JS hooks
src/App.vue

source: blog
Applying
Motion




Example
List Example
<div class="buttons-container">
<button @click="add()">+</button>
<button @click="remove(0)" :disabled="list.length===0">-</button>
</div>
<div class="list-container">
<transition-group name="list">
<div class="box"
v-for="(item, index) in list"
:key="item">
</div>
</transition-group>
</div>
src/app.component.ts
Animating new items
src/App.vue
.list-enter-to
Animating new items
scale(1)
opacity 1
scale(0.5)
opacity 0
.list-enter
.list-enter-active
add
<style>
.list-enter {
transform: scale(0.5);
opacity: 0;
}
.list-enter-to {
transform: scale(1);
opacity: 1;
}
.list-enter-active {
transition: transform 1s cubic-bezier(0.8,-0.6,0.2,1.5),
opacity 1s cubic-bezier(0.8,-0.6,0.2,1.5);
}
</style>
src/app.component.ts
New items transitions
src/App.vue
Animating removed items
STATE
STATE
scale(0.5)
opacity 0
scale(1)
opacity 1
.list-leave-active
remove
.list-leave-to
.list-leave
<style>
.box.list-leave {
transform: scale(1); opacity: 1;
height: 50px; margin: 5px;
}
.box.list-leave-to {
transform: scale(0.5); opacity: 0;
height: 0px; margin: 0px;
}
.list-leave-active {
transition: transform 1s cubic-bezier(0.8,-0.6,0.2,1.5),
opacity 1s cubic-bezier(0.8,-0.6,0.2,1.5),
height 1s cubic-bezier(0.8,-0.6,0.2,1.5),
margin 1s cubic-bezier(0.8,-0.6,0.2,1.5);
}
</style>
src/app.component.ts
Removed items transitions
src/App.vue
Stagger
time
<div class="list-container">
<transition-group name="list"
appear @appear="this.stagger = true">
<div class="box"
v-for="(item, index) in list" :key="item"
:style="this.stagger?null:{transitionDelay: index*300+'ms'}">
</div>
</transition-group>
</div>
src/app.component.ts
Stagger initial render
src/App.vue
More
Influencers



David Khourshid
Issara Willenskomer
Sarah Drasner

Rachel Nabors

Implementing Motion using Vue
By Gerard Sans
Implementing Motion using Vue
In this talk we are going to cover some of the 12 principles behind UX Motion giving practical examples. We will focus on these principles while covering some implementation details.
- 3,841