How to Create the VueConf Load Animation (While Learning About Vue.js and SVGs in the Process)

Michael Mangialardi
Mar 10, 2017 · 34 min read
Image for post
Image for post

Vue.js recently released a new landing page for VueConf 2017 that included a nice animation. How about we make that animation and learn more about Vue.js and SVGs?



The Backstory

Image for post
Image for post
Image for post
Image for post

Breaking Down the Problem


Breaking Down the Graphics We Need to Create

Image for post
Image for post

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Creating the Graphics

Image for post
Image for post
Image for post
Image for post
#41B883
Image for post
Image for post
Image for post
Image for post
#35495E
Image for post
Image for post
#FFFFFF
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
//green
#41B883
//white
#FFFFFF
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
#41B883
Image for post
Image for post
Image for post
Image for post

Trees

#35495E
Image for post
Image for post
Image for post
Image for post
#41B883
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Exporting As SVGs

Image for post
Image for post

Preparing to Import

Importing the SVGs


Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
<svg width="100%" height="100%" viewBox="0 0 191 169" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g id="VueConf-Logo"><path id="VueConf-Triangle-Green" d="M95.04,0l95.04,168l-190.08,0l95.04,-168Z" style="fill:#41b883;"/><path id="VueConf-Triangle-Blue" d="M95.04,67.95l61.624,100.05l-123.248,0l61.624,-100.05Z" style="fill:#35495e;"/><path id="VueConf-Triangle-White" d="M95.04,122.992l27.51,45.968l-55.02,0l27.51,-45.968Z" style="fill:#fff;"/></g></svg>
<svg width="100%" height="100%" viewBox="0 0 89 87" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g id="Sun"><ellipse id="Sun-Circle-White" cx="44.28" cy="43.2" rx="44.28" ry="43.2" style="fill:#41b883;"/><ellipse id="Sun-Circle-Green" cx="44.28" cy="43.2" rx="26.88" ry="26.224" style="fill:#fff;"/></g></svg>
<svg width="100%" height="100%" viewBox="-5 -20 70 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g id="Bush"><ellipse id="Bush-Circle-Left" cx="29.4" cy="28.32" rx="29.4" ry="28.32" style="fill:#41b883;"/><ellipse id="Bush-Circle-Right" cx="58.8" cy="38.76" rx="18.562" ry="17.88" style="fill:#41b883;"/></g></svg>
<svg width="100%" height="100%" viewBox="0 -10 77 147" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g id="Tree-Left"><rect id="Tree-Left-Base" x="33.171" y="42.234" width="10.32" height="104.766" style="fill:#35495e;"/><ellipse id="Tree-Left-Circle-Bottom" cx="38.331" cy="59.367" rx="38.331" ry="36.122" style="fill:#41b883;"/><ellipse id="Tree-Left-Circle-Top" cx="38.331" cy="23.245" rx="24.666" ry="23.245" style="fill:#41b883;"/></g></svg>
<svg width="100%" height="100%" viewBox="0 -16 110 171" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g id="Tree-Right"><rect id="Tree-Right-Base" x="49.615" y="54.96" width="10.32" height="115.2" style="fill:#35495e;"/><ellipse id="Tree-Right-Circle" cx="54.775" cy="56.76" rx="54.775" ry="56.76" style="fill:#41b883;"/></g></svg>
<ellipse id="Tree-Right-Circle" cx="54.775" cy="56.76" rx="54.775" ry="56.76" style="fill:#41b883;"/><rect id="Tree-Right-Base" x="49.615" y="54.96" width="10.32" height="115.2" style="fill:#35495e;"/>
<ellipse id="Tree-Right-Circle" cx="54.775" cy="56.76" rx="54.775" ry="56.76" style="fill:#41b883;"/><rect id="Tree-Right-Base" x="49.615" y="54.96" width="10.32" height="115.2" style="fill:#35495e;"/>
<div class="box">

<svg class="vueconf-logo" viewBox="0 0 191 169" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g id="VueConf-Logo"><path id="VueConf-Triangle-Green" d="M95.04,0l95.04,168l-190.08,0l95.04,-168Z" style="fill:#41b883;"/><path id="VueConf-Triangle-Blue" d="M95.04,67.95l61.624,100.05l-123.248,0l61.624,-100.05Z" style="fill:#35495e;"/><path id="VueConf-Triangle-White" d="M95.04,122.992l27.51,45.968l-55.02,0l27.51,-45.968Z" style="fill:#fff;"/></g></svg>

</div>
sun
bush
tree-left
tree-right
Image for post
Image for post

Breaking Down the Animation



Writing the CSS Animations

//1
Slides down from the top of the screen, drops down just past the vertical and horizontal center point and bounces up a tad higher.
//2
Flips 180 degrees.
@keyframes slideInBounce {

}
@keyframes slideInBounce {

0%{
top: -100%;
}
70%{
top: 33%;
}
100%{
top: 25%;
}
}
@keyframes slideInBounce {
0%{
transform: rotate(-180deg);
top: -100%;
}
70%{
transform: rotate(-180deg);
top: 33%;
}
100%{
transform: rotate(-180deg);
top: 25%;
}
}
.vueconf-logo {
position: absolute;
height: 50%;
width: 50%;
top: 25%;
left: 25%;
animation: slideInBounce 1s 1;
}
Image for post
Image for post
@keyframes flip{
0%{
transform: rotate(-180deg);
}
100%{
transform: rotate(0deg);
}
}
.vueconf-logo {
position: absolute;
height: 50%;
width: 50%;
top: 25%;
left: 25%;
animation: slideInBounce 1s 1,
flip 0.5s 1s 1;

}
Image for post
Image for post
Tree Left Base grows vertically then the Tree Left Circle Bottom spawns.
var app = new Vue({
el: "#app",
data: {

},
methods: {

}
})
<div id="app"><!-- Everything else stays here --></div>
var app = new Vue({
el: "#app",
data: {
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false

},
methods: {

}
})
<svg class="tree-left" viewBox="0 0 77 147" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g id="Tree-Left"><rect v-if="showTreeLeftBase" id="Tree-Left-Base" x="33.171" y="42.234" width="10.32" height="104.766" style="fill:#35495e;"/><ellipse v-if="showTreeLeftCircleBottom" id="Tree-Left-Circle-Bottom" cx="38.331" cy="59.367" rx="38.331" ry="36.122" style="fill:#41b883;"/><ellipse v-if="showTreeLeftCircleTop" id="Tree-Left-Circle-Top" cx="38.331" cy="23.245" rx="24.666" ry="23.245" style="fill:#41b883;"/></g></svg>
Image for post
Image for post
var app = new Vue({
el: "#app",
data: {
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false
},
methods: {

},
created () {
//call a function that will set setTreeLeftBase to true
}
})
var app = new Vue({
el: "#app",
data: {
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false
},
methods: {
toggleTreeLeftBase: function () {

}
},
created () {
this.toggleTreeLeftBase()
}
})
var app = new Vue({
el: "#app",
data: {
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false
},
methods: {
toggleTreeLeftBase: function () {
this.showTreeLeftBase = true
}
},
created () {
this.toggleTreeLeftBase()
}
})
Image for post
Image for post
created () {
setTimeout( () => {
this.toggleTreeLeftBase()
}, 2000)
}
Image for post
Image for post
.growActive{

}
@keyframes grow{

}
@keyframes grow{
0%{
height: 0%;
}
100%{
height: 100%;
}
}
.growActive{
animation: grow 0.5s 1;
}
<rect v-bind:class="treeLeftBaseClass" v-if="showTreeLeftBase" id="Tree-Left-Base" x="33.171" y="42.234" width="10.32" height="104.766" style="fill:#35495e;"/>
var app = new Vue({
el: "#app",
data: {
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: ""
},
methods: {
toggleTreeLeftBase: function () {
this.showTreeLeftBase = true
}
},
created () {
setTimeout( () => {
this.toggleTreeLeftBase()
}, 2000)
}
})
var app = new Vue({
el: "#app",
data: {
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: ""
},
methods: {
toggleTreeLeftBase: function () {
this.showTreeLeftBase = true
this.treeLeftBaseClass = "growActive"
}
},
created () {
setTimeout( () => {
this.toggleTreeLeftBase()
}, 2000)
}
})
Image for post
Image for post
@keyframes grow{
0%{
transform: translatey(100%);

}
100%{
transform: translatey(0%);
}
}
Image for post
Image for post
.tree-left{
position: absolute;
height: 25%;
width: 30%;
left: 0%;
bottom: 25%;
border: solid red 3px;
}
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
@keyframes grow{
0%{
transform: translatey(100%);

}
100%{
transform: translatey(0%);
}
}

Image for post
Image for post

Tree Left Circle Bottom grows and shrinks.
methods: {
toggleTreeLeftBase: function () {
this.showTreeLeftBase = true
this.treeLeftBaseClass = "growActive"
setTimeout( () => {

}, 500)

}
},
created () {
setTimeout( () => {
this.toggleTreeLeftBase()
}, 2000)
}
methods: {
toggleTreeLeftBase: function () {
this.showTreeLeftBase = true
this.treeLeftBaseClass = "growActive"
setTimeout( () => {
this.toggleTreeLeftCircleBottom()
}, 500)
}
},
toggleTreeLeftCircleBottom: function () {  this.showTreeLeftCircleBottom = true}
Image for post
Image for post
<ellipse v-bind:class="treeLeftCircleBottomClass" v-if="showTreeLeftCircleBottom" id="Tree-Left-Circle-Bottom" cx="38.331" cy="59.367" rx="38.331" ry="36.122" style="fill:#41b883;"/>
data: {
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: "",
treeLeftCircleBottomClass: ""
},
toggleTreeLeftCircleBottom: function () {  this.showTreeLeftCircleBottom = true
this.treeLeftCircleBottomClass = "growShrinkActive"
}
.growShrinkActive{

}
@keyframes growShrink{
0% {
transform-origin: center;
transform: scale(0);
}
50% {
transform-origin: center;
transform: scale(1.3);
}
100% {
transform-origin: center;
transform: scale(1);
}
}
.growShrinkActive{
animation: growShrink 0.5s 1;
}
Image for post
Image for post
toggleTreeLeftCircleBottom: function () {   this.showTreeLeftCircleBottom = true
this.treeLeftCircleBottomClass = "growShrinkActive"
setTimeout( () => {
this.toggleTreeLeftCircleTop()
}, 500)

}
toggleTreeLeftCircleTop: function () {
this.showTreeLeftCircleTop = true
}
<ellipse v-bind:class="treeLeftCircleTopClass" v-if="showTreeLeftCircleTop" id="Tree-Left-Circle-Top" cx="38.331" cy="23.245" rx="24.666" ry="23.245" style="fill:#41b883;"/>
data: {
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: "",
treeLeftCircleBottomClass: "",
treeLeftCircleTopClass: ""
},
toggleTreeLeftCircleTop: function () {
this.showTreeLeftCircleTop = true
this.treeLeftCircleTopClass= "growShrinkActive"
}
Image for post
Image for post
Right after Tree Left Base fully grows, Tree Right Base spawns and starts to grow.
data: {
//treeLeft stuff
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: "",
treeLeftCircleBottomClass: "",
treeLeftCircleTopClass: "",
//treeRight stuff
showTreeRightBase: false,
showTreeRightCircle: false

},
<rect v-if="showTreeRightBase" id="Tree-Right-Base" x="49.615" y="54.96" width="10.32" height="115.2" style="fill:#35495e;"/><ellipse v-if="showTreeRightCircle" id="Tree-Right-Circle" cx="54.775" cy="56.76" rx="54.775" ry="56.76" style="fill:#41b883;"/>
Image for post
Image for post
    toggleTreeLeftBase: function () {
this.showTreeLeftBase = true
this.treeLeftBaseClass = "growActive"
setTimeout( () => {
this.toggleTreeLeftCircleBottom()
this.toggleTreeRightBase()
}, 500)
},
toggleTreeRightBase: function() {
this.showTreeRightBase = true
}
<rect v-bind:class="treeRightBaseClass" v-if="showTreeRightBase" id="Tree-Right-Base" x="49.615" y="54.96" width="10.32" height="115.2" style="fill:#35495e;"/>
data: {
//treeLeft stuff
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: "",
treeLeftCircleBottomClass: "",
treeLeftCircleTopClass: "",
//treeRight stuff
showTreeRightBase: false,
showTreeRightCircle: false,
treeRightBaseClass: ""
},
  toggleTreeRightBase: function() {
this.showTreeRightBase = true
this.treeRightBaseClass= "growShrinkActive"
}
Image for post
Image for post
    toggleTreeRightBase: function() {
this.showTreeRightBase = true
this.treeRightBaseClass= "growShrinkActive"
setTimeout(() => {
this.toggleTreeRightCircle()
}, 500)

}
toggleTreeRightCircle: function () {
this.showTreeRightCircle = true
}
Image for post
Image for post
<ellipse v-if="showTreeRightCircle" v-bind:class="treeRightCircleClass" id="Tree-Right-Circle" cx="54.775" cy="56.76" rx="54.775" ry="56.76" style="fill:#41b883;"/>
data: {
//treeLeft stuff
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: "",
treeLeftCircleBottomClass: "",
treeLeftCircleTopClass: "",
//treeRight stuff
showTreeRightBase: false,
showTreeRightCircle: false,
treeRightBaseClass: "",
treeRightCircleClass: ""
},
    toggleTreeRightCircle: function () {
this.showTreeRightCircle = true
this.treeRightCircleClass = "growShrinkActive"
}
Image for post
Image for post

Bush Circle Left grows and shrinks.Bush Circle Right starts growing when Bush Circle Left fully grows and then shrinks.//timing
When Tree Right Base fully grows, Bush Circle Left spawns and grows.
<ellipse v-if="showBushCircleLeft" id="Bush-Circle-Left" cx="29.4" cy="28.32" rx="29.4" ry="28.32" style="fill:#41b883;"/><<ellipse v-if="showBushCircleRight" id="Bush-Circle-Right" cx="58.8" cy="38.76" rx="18.562" ry="17.88" style="fill:#41b883;"/>
data: {
//treeLeft stuff
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: "",
treeLeftCircleBottomClass: "",
treeLeftCircleTopClass: "",
//treeRight stuff
showTreeRightBase: false,
showTreeRightCircle: false,
treeRightBaseClass: "",
treeRightCircleClass: "",
//bush stuff
showBushCircleLeft: "",
showBushCircleRight: ""

},
Image for post
Image for post
toggleTreeLeftBase: function () {
this.showTreeLeftBase = true
this.treeLeftBaseClass = "growActive"
setTimeout( () => {
this.toggleTreeLeftCircleBottom()
this.toggleTreeRightBase()
this.toggleBushCircleLeft()
}, 500)
},
//latertoggleBushCircleLeft: function () {
this.showBushCircleLeft = true
},
Image for post
Image for post
<ellipse v-bind:class="bushCircleLeftClass" v-if="showBushCircleLeft" id="Bush-Circle-Left" cx="29.4" cy="28.32" rx="29.4" ry="28.32" style="fill:#41b883;"/>
data: {
//treeLeft stuff
showTreeLeftBase: false,
showTreeLeftCircleBottom: false,
showTreeLeftCircleTop: false,
treeLeftBaseClass: "",
treeLeftCircleBottomClass: "",
treeLeftCircleTopClass: "",
//treeRight stuff
showTreeRightBase: false,
showTreeRightCircle: false,
treeRightBaseClass: "",
treeRightCircleClass: "",
//bush stuff
showBushCircleLeft: "",
showBushCircleRight: "",
bushCircleLeftClass: ""
},
//under methods
toggleBushCircleLeft: function () {
this.showBushCircleLeft = true
this.bushCircleLeftClass = "growShrinkActive"
},
Image for post
Image for post
<ellipse v-if = "showBushCircleRight"  v-bind:class="bushCircleRightClass" id="Bush-Circle-Right" cx="58.8" cy="38.76" rx="18.562" ry="17.88" style="fill:#41b883;"/>
    //under data    //bush stuff
showBushCircleLeft: "",
showBushCircleRight: "",
bushCircleLeftClass: "",
bushCircleRightClass: ""
//methods toggleBushCircleLeft: function () {
this.showBushCircleLeft = true
this.bushCircleLeftClass = "growShrinkActive"
//wait 0.25 seconds since we want Bush Circle Right to spawn when Bush Circle Left grows
setTimeout(() => {
this.toggleBushCircleRight()
}, 250)

},
toggleBushCircleRight: function () {
this.showBushCircleRight = true
this.bushCircleRightClass = "growShrinkActive"
},
Image for post
Image for post
Sun grows and shrinks.//timingSun spawns and starts growing when Tree Right is complete.
<svg v-if="showSun" class="sun" viewBox="0 0 89 87" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
//under data//sun stuff
showSun: false
   toggleTreeRightCircle: function () {
this.showTreeRightCircle = true
this.treeRightCircleClass = "growShrinkActive"
setTimeout(() =>{
this.toggleSun()
},250)
//we wait 250 seconds since that is the point when Tree Right Circle grows and not when it ends
},
//later toggleSun: function () {
this.showSun = true
}
<svg v-bind:class="sunClass" v-if="showSun" viewBox="0 0 89 87" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
//data
//sun stuff
showSun: false,
sunClass : "sun"
//methods
toggleSun: function () {
this.showSun = true
this.sunClass = "sun growShrinkActive"
}
Image for post
Image for post

Concluding Thoughts


Coding Artist

Providing journeys for developers who see the web as their…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store