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)
}The time the animation lasts (in ms)
A function progress -> value multipler
A function that modifies a value based on the results from the delta function
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)
}
}// 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
}
}
}<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>function circ (progress) {
return 1 - Math.sin(Math.acos(progress))
}function circ (progress) {
return Math.pow(progress, 2)
}function back (x) {
// x - defines the pushback distance
return progress => Math.pow(progress, 2) * ((x + 1) * progress - x)
}Back – also called bow function
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...
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
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
animate(
600,
circEaseInOut,
createStep(this.element, 'posY', 0, 100)
)Cool but what if we want to chain the animations?
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!
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
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
function createAnimation (duration, delta, step) {
return () => animate(duration, delta, step)
}One to defer the animation execution
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
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))
}http://jsfiddle.net/q5gwf3ba/7/