Skip to content

Instantly share code, notes, and snippets.

@loklaan
Last active May 8, 2018 06:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save loklaan/fb0e7cb0d67326d60dc4381ba3a19863 to your computer and use it in GitHub Desktop.
Save loklaan/fb0e7cb0d67326d60dc4381ba3a19863 to your computer and use it in GitHub Desktop.
svg.js meets Hitch Logo

SVG Animation Attempts

Approaches Taken

  1. Tried Wilderness - wasn't documented well enough
  2. Tried pure css @keyframes - animations too complex to express with abstraction
  3. Tried SVG SMIL - pretty good but Edge doesn't support
  4. Quickly tried svg libs that can animate & ease:
    • Attributes like fill, opacity, etc
    • Points from a path, eg. d attr
  5. Moved ahead with using svg.js

Experiences using svg.js

  1. Get svg's from each keyframe
  2. Convert the shapes in the svg to compound paths
  3. Use svg.js + svg.pathmorphing.js
    • Recreate svg: foo = draw.path('points...').attr({})
    • Animate between keyframes: foo.animate(duration, ease).plot('points...')

Notes

  • Principle doesn't have export to svg, lame tool.
  • In Illustrator sometimes the path is plot the wrong way between illustrator (keyframe) edits, so your svg.js animation "morphs" might be broken - start from last keyframe and remake the shape; continue.
  • Potentially can get very quick and converting SVGs & easing values to js code
<script src="https://unpkg.com/svg.js"></script>
<script src="https://unpkg.com/svg.pathmorphing.js@0.1.0"></script>
<script src="https://unpkg.com/svg.easing.js"></script>
<button id="reset">Reset</button>
<div id="drawing"></div>
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
const pipe = (headFn, ...restFns) => (...args) => restFns.reduce(
(value, fn) => fn(value),
headFn(...args)
);
const compose = (...fns) => pipe(...fns.reverse());
const draw = SVG('drawing').viewbox(0, 0, 180, 120);
const DURATION = {
FIRST: 600,
SECOND: 500,
RESET: 500
};
const TRANSITION = {
FIRST: '>',
SECOND: 'bounce',
RESET: 'bounce'
};
const SHADOW_PATHS = [
'M64,100H116a5,5,0,0,1,5,5V105a5,5,0,0,1-5,5H64a5,5,0,0,1-5-5V105A5,5,0,0,1,64,100Z',
'M27.5,101h119a8.5,8.5,0,0,1,8.5,8.5h0a8.5,8.5,0,0,1-8.5,8.5H27.5a8.5,8.5,0,0,1-8.5-8.5h0A8.5,8.5,0,0,1,27.5,101Z',
'M29.75,80.52l93.83-3a4.75,4.75,0,0,1,4.89,4.59h0A4.75,4.75,0,0,1,123.88,87L30,90a4.75,4.75,0,0,1-4.89-4.59h0A4.75,4.75,0,0,1,29.75,80.52Z'
];
const RIGHT_PILL_PATHS = [
'M100,93.5v-71A12.5,12.5,0,0,1,112.5,10h0A12.5,12.5,0,0,1,125,22.5v71A12.5,12.5,0,0,1,112.5,106h0A12.5,12.5,0,0,1,100,93.5Z',
'M152.08,72.24l-68-19.12a12.69,12.69,0,0,1-8.78-15.65h0A12.69,12.69,0,0,1,91,28.69l68,19.12a12.69,12.69,0,0,1,8.78,15.65h0A12.69,12.69,0,0,1,152.08,72.24Z',
'M151.4,71.34H76.08A9.08,9.08,0,0,1,67,62.26h0a9.08,9.08,0,0,1,9.08-9.08H151.4a9.08,9.08,0,0,1,9.08,9.08h0A9.08,9.08,0,0,1,151.4,71.34Z'
];
const LEFT_PILL_PATHS = [
'M55,93.5v-71A12.54,12.54,0,0,1,67.5,10h0A12.54,12.54,0,0,1,80,22.5v71A12.54,12.54,0,0,1,67.5,106h0A12.54,12.54,0,0,1,55,93.5Z',
'M13.09,72.53,44.3,9.18a12.73,12.73,0,0,1,17-5.77h0a12.73,12.73,0,0,1,5.77,17L35.85,83.74a12.73,12.73,0,0,1-17,5.77h0A12.73,12.73,0,0,1,13.09,72.53Z',
'M28.24,59.77l67.07-22.1a12.73,12.73,0,0,1,16,8.08h0a12.73,12.73,0,0,1-8.08,16L36.19,83.88a12.73,12.73,0,0,1-16-8.08h0A12.73,12.73,0,0,1,28.24,59.77Z'
];
const MIDDLE_PILL_PATHS = [
'M125.5,70h-71A12.5,12.5,0,0,1,42,57.5h0A12.5,12.5,0,0,1,54.5,45h71A12.5,12.5,0,0,1,138,57.5h0A12.5,12.5,0,0,1,125.5,70Z',
'M107.29,81.33,39.37,62a12.69,12.69,0,0,1-8.73-15.68h0A12.69,12.69,0,0,1,46.31,37.6l67.93,19.32A12.69,12.69,0,0,1,123,72.59h0A12.69,12.69,0,0,1,107.29,81.33Z',
'M110.78,82.51,46,54.31a12.69,12.69,0,0,1-6.57-16.7h0A12.69,12.69,0,0,1,56.17,31l64.75,28.19a12.69,12.69,0,0,1,6.57,16.7h0A12.69,12.69,0,0,1,110.78,82.51Z'
];
const shadow = draw
.path(SHADOW_PATHS[0]).attr({ fill: '#2a4f5b', opacity: 0.15 })
const right = draw
.path(RIGHT_PILL_PATHS[0]).attr({ fill: '#8340df' })
const left = draw
.path(LEFT_PILL_PATHS[0]).attr({ fill: '#3892f7' })
const middle = draw
.path(MIDDLE_PILL_PATHS[0]).attr({ fill: '#ed3272' })
let animated = false;
function animate() {
shadow
.animate(DURATION.FIRST, TRANSITION.FIRST)
.attr({ opacity: 0.03 })
.plot(SHADOW_PATHS[1])
.animate(DURATION.SECOND, TRANSITION.SECOND)
.plot(SHADOW_PATHS[2])
.attr({ opacity: 0.15 })
right
.animate(DURATION.FIRST, TRANSITION.FIRST)
.plot(RIGHT_PILL_PATHS[1])
.animate(DURATION.SECOND + 400, TRANSITION.SECOND)
.plot(RIGHT_PILL_PATHS[2])
left
.animate(DURATION.FIRST, TRANSITION.FIRST)
.plot(LEFT_PILL_PATHS[1])
.animate(DURATION.SECOND, TRANSITION.SECOND)
.plot(LEFT_PILL_PATHS[2])
middle
.animate(DURATION.FIRST, TRANSITION.FIRST)
.plot(MIDDLE_PILL_PATHS[1])
.animate(DURATION.SECOND + 200, TRANSITION.SECOND)
.plot(MIDDLE_PILL_PATHS[2])
animated = true;
}
function resetAnimate() {
shadow.stop();
left.stop();
right.stop();
middle.stop();
shadow.animate(DURATION.RESET, TRANSITION.RESET)
.attr({
fill: '#2a4f5b',
opacity: 0.15
})
.plot(SHADOW_PATHS[0])
right.animate(DURATION.RESET, TRANSITION.RESET)
.attr({
fill: '#8340df'
})
.plot(RIGHT_PILL_PATHS[0])
left.animate(DURATION.RESET, TRANSITION.RESET)
.plot(LEFT_PILL_PATHS[0]).attr({
fill: '#3892f7'
})
middle.animate(DURATION.RESET, TRANSITION.RESET)
.attr({
fill: '#ed3272'
})
.plot(MIDDLE_PILL_PATHS[0])
}
let timer;
document.querySelector('#reset').addEventListener('click', () => {
if (animated) {
resetAnimate();
clearTimeout(timer);
timer = setTimeout(() => {
animate()
}, DURATION.RESET + 150)
} else {
animate();
}
})
#reset {
width: 100%;
padding: 20px;
margin-bottom: 20px;
}
#drawing {
width: 100%;
height: 120px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment