Build your own animation... engine?
With a little bit of Vue.js
The base
function animate (duration, delta, step) {
// set start time
const start = performance.now() // 3606016.2050000005
// create function that accepts current time
const animation = function (t) {
// currentTime - startTime gives us
// the time that already passed from start
// dividing by the duration gives us 0 < progress < 1
let progress = (t - start) / duration
if (progress > 1) progress = 1
// run the stepFunction
// modify the progress with delta fun
step(delta(progress))
// if progress !== 1 repeat the whole thing
if (progress !== 1) {
window.requestAnimationFrame(animation)
}
}
// start the animation
// requestAnimationFrame calls the callback with
// the new time
window.requestAnimationFrame(animation)
}Duration
The time the animation lasts (in ms)
Delta
A function progress -> value multipler
Step
A function that modifies a value based on the results from the delta function
createStep
function createStep (
obj, // source object
property, // property to modify
from, // initial value when progress == 0
to // end value when progress == 1
) {
// calculate the absolute difference
const absTo = Math.abs(to - from)
// return function that accepts progress (0-1)
return progress => {
// depending on which value is greater
obj[property] = to > from
// increase the property’s value
? from + (absTo * progress)
// decrease the property’s value
: from - (absTo * progress)
}
}Vue.js part - data model
// Create the data model that we can work on
data () {
return {
logoDegrees: 180,
logoY: -100,
logoOpacity: 0,
sunRadius: 0,
typo: {
v: 25,
u: 25,
e: 25,
c: 25,
o: 25,
n: 25,
f: 25
}
}
}Vue.js part - SVG template
<template>
<!-- an SVG file that can have dynamic properties like :r here -->
<g transform="matrix(0.25,0,0,0.25,577.25,193.34)">
<circle
cx="363"
cy="715"
<!-- value from the model -->
:r="sunRadius"
style="fill:none;stroke:rgb(58,185,130);stroke-width:15.22px;"/>
</g>
</template>delta
function circ (progress) {
return 1 - Math.sin(Math.acos(progress))
}function circ (progress) {
return Math.pow(progress, 2)
}

delta
function back (x) {
// x - defines the pushback distance
return progress => Math.pow(progress, 2) * ((x + 1) * progress - x)
}
Back – also called bow function
delta
function bounce (progress) {
for(var a = 0, b = 1, result; 1; a += b, b /= 2) {
if (progress >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2);
}
}
}And there is bounce...

reverse functions
function makeEaseOut (delta) {
return function (progress) {
return 1 - delta(1 - progress)
}
}By default all the functions are so called EaseIn
Sometimes we want to reverse a function. This is then called EaseOut
You might remember this from CSS animations.

Red – EaseIn
Green – EaseOut
mixed functions
function makeEaseInOut (delta) {
return function (progress) {
if (progress < .5) {
return delta(2*progress) / 2
}
else {
return (2 - delta(2*(1-progress))) / 2
}
}
}
// usage
const circEaseInOut = makeEaseInOut(circ)You can merge EaseIn and EaseOut.
This is then called EaseInOut.

Red – EaseIn
Green – EaseOut
Blue - EaseInOut
Usage
animate(
600,
circEaseInOut,
createStep(this.element, 'posY', 0, 100)
)Cool but what if we want to chain the animations?
Chaining
animate(
600,
circEaseInOut,
createStep(this.element, 'posY', 0, 100)
)
// so we can do something like
animate(
600,
circEaseInOut,
createStep(this.element, 'posY', 0, 100)
).then(
animate(400, circ, createStep(this.element, 'posY', 100, 300))
)Chaining would require to wait for the previous animation to complete.
So my first thought was – Promises!
Chaining
function animate (duration, delta, step) {
return new Promise( // return a Promise
function (resolve, reject) {
const start = performance.now()
const animation = function (t) {
let progress = (t - start) / duration
if (progress > 1) progress = 1
step(delta(progress))
if (progress !== 1) {
window.requestAnimationFrame(animation)
} else {
// Resolve the Promise when
// animation is complete
resolve()
}
}
window.requestAnimationFrame(animation)
}
)
}So lets change our animate function to a Promise
Some helpers
function wait (duration) {
return new Promise(resolve => {
setTimeout(() => resolve(), duration))
}
}One so we can wait for a given time
function combineSteps (...steps) {
return function (progress) {
steps.forEach(step => step(progress))
}
}One to combine steps
Some helpers
function createAnimation (duration, delta, step) {
return () => animate(duration, delta, step)
}One to defer the animation execution
Some helpers
function* toGenerator (iterable) {
yield* iterable // hue hue
}
function stagger (animations, delay) {
// transform the animations array to generator
const animationsGenerator = toGenerator(animations)
async function animateNext () {
// get the new animation from the generator
const { value: animation, done } = animationsGenerator.next()
// if generator is not yet complete
if (!done) {
// run the animation
animation()
// wait for the delay
await wait(delay)
// repeat
animateNext()
}
}
// start!
animateNext()
}Two to create the stagger effect
Example animations
async animateLogo () {
await animate(
2200,
backEaseOut(2),
combineSteps(
createStep(this, 'logoY', -100, 0),
createStep(this, 'logoOpacity', 0, 1)
)
)
await wait(400) // pause for 400ms
await animate(600, circEaseInOut, createStep(this, 'logoDegrees', 180, 360))
return
}Yup. Since our animations are promises we can await them
async animateTypo () {
const duration = 500
stagger([
createAnimation(duration, backEaseOut(1), combineSteps(
createStep(this.typo, 'vO', 0, 1),
createStep(this.typo, 'v', 25, 0)
)),
createAnimation(duration, backEaseOut(1), combineSteps(
createStep(this.typo, 'uO', 0, 1),
createStep(this.typo, 'u', 25, 0)
)),
createAnimation(duration, backEaseOut(1), combineSteps(
createStep(this.typo, 'eO', 0, 1),
createStep(this.typo, 'e', 25, 0)
)),
createAnimation(duration, backEaseOut(2), combineSteps(
createStep(this.typo, 'cO', 0, 1),
createStep(this.typo, 'c', 25, 0)
)),
createAnimation(duration, backEaseOut(2), combineSteps(
createStep(this.typo, 'oO', 0, 1),
createStep(this.typo, 'o', 25, 0)
)),
createAnimation(duration, backEaseOut(2), combineSteps(
createStep(this.typo, 'nO', 0, 1),
createStep(this.typo, 'n', 25, 0)
)),
createAnimation(duration, backEaseOut(2), combineSteps(
createStep(this.typo, 'fO', 0, 1),
createStep(this.typo, 'f', 25, 0
)))
], 100)
await wait(500)
animate(duration, backEaseOut(3), createStep(this, 'dotRadius', 0, 1.5))
}Result
http://jsfiddle.net/q5gwf3ba/7/
Thank you! :)

Build a simple animation engine with Vue.js
By Damian Dulisz
Build a simple animation engine with Vue.js
Build a simple animation engine with a little bit of Vue.js
- 5,579