Intricate SVG Animations
Generate, San Francisco, July 2016
Sarah Drasner
Why?
- Animation is powerful to convey meaning
- Can guide your users
- Can retain context
- Cuts down on perceived wait time
- If built correctly, animation can be useful and performant
- Because otherwise we're not using the web to its full potential
- FUN!
- (Don't overdo it)
SVG!
- Crisp on any display
- Less HTTP requests to handle
- Easily scalable for responsive
- Small filesize if you design for performance
- Easy to animate
- Easy to make accessible
- Fun! (there's a pattern here)
SVG!
This pen.
Before we get started:
Optimize!
illustrator: export as (not save as)
Before we get started:
Animation Performance!
- People expect mobile to be faster than web
- JankFree.org
- Advanced Performance Audits with DevTools by Paul Irish
- CSS-Tricks Article: Weighing SVG Animation Techniques with Benchmarks
- Above all else: test things yourself!
how does animation help?
anticipatory cues
Custom Loaders:
Viget did an experiment and found that despite some individual variation, novel loaders as a whole had a higher wait time and lower abandon rate than generic ones
22 sec
14 sec
From this CSS-Tricks Article
$quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
$quad-out: cubic-bezier(0.55, 0.085, 0.68, 0.53);
.magnifier circle, .magnifier line, .x-out line {
fill: none;
stroke: #fff;
stroke-linecap: round;
transition: 0.25s all $quad-out;
}
.magnifier {
line {
transition: 0.25s all $quad-out;
transform: rotate(0deg) translateY(0px);
}
circle {
transition: 0.25s all $quad-out;
transform: scale(1);
}
}
$( document ).ready(function() {
$(".main").on("click", function() {
var magLine = $(this).find(".magnifier line"),
mainInput = $(this).find("input");
if ($(this).hasClass("open")) {
$(this).removeClass("open");
magLine.attr("x2", 33.1);
mainInput.val("");
} else {
$(this).addClass("open");
magLine.attr("x2", 300);
mainInput.focus();
}
});
});
This pen.
Main Principle:
Design everything first, slowly unveil things.
Ugly storyboards save you time!
correct tools for the job
my recommendations:
CSS/SCSS
- Small sequences and simple interactions
- Once you get more than 3... switch to:
GSAP (GreenSock)
- Great for sequencing and complex movement
- Cross-browser consistency
React-Motion
- Great for single movements that you'd like to look realistic
- Snap.svg is more like jQuery for SVG
- Web Animations API looks great, still waiting on support
- Velocity is similar to GSAP with less bells and whistles
- Mo.js looks great and is still in beta
Let's make some cool stuff.
GreenSock/GSAP
Solves Cross-Browser Inconsistencies
Bad transform origin bug on rotation, now solved in Firefox.
More in this CSS-Tricks article.
Chrome
IE
Firefox
Safari (zoomed)
Timeline
- stack tweens
- set them a little before and after one another
- change their placement in time
- group them into scenes
- add relative labels
- animate the scenes!
- make the whole thing faster, move the placement of the whole scene, nesting
All without recalculation!
The issue with longer CSS animations:
This pen.
How do we do it?
This pen.
Other Cool Things
for Complex Animation
- Motion along a path (widest support)
- Draggable
- CSS Properties
- Draw SVG- make an SVG look like it draws itself.
- MorphSVG
- Relative Color Tweening
- CycleStagger
Relative Color Tweening
This pen.
Incrementing
//button hue
function hued() {
var ch1 = "hsl(+=110%, +=0%, +=0%)",
tl = new TimelineMax({
paused: true
});
tl.add("hu");
tl.to(mult, 1.25, {
fill: ch1
}, "hu");
...
tl.to(body, 1.25, {
backgroundColor: ch1
}, "hu");
return tl;
}
var hue = hued();
Relative Color Tweening
This pen.
DrawSVG
DrawSVG
Plugin for GSAP, very simple code.
TweenMax.staggerFromTo($draw, 4,{ drawSVG:'0' }, { drawSVG: true }, 0.1);
Under the Hood
Done with stroke-dasharray and stroke-dashoffset
- Path or shape has a stroke
- The stroke is dashed
- Use JS to .getTotalLength()
- Dasharray the whole length of the shape
- Animate dashoffset
@keyframes dash {
50% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: -274;
}
}
This pen.
Motion Along a Path
Motion Along a Path for Realism
This pen.
TweenMax.to($firefly1, 6, {
bezier: {
type:"soft",
values:[{x:10, y:30}, {x:-30, y:20}, {x:-40, y:10},
{x:30, y:20}, {x:10, y:30}],
autoRotate:true
},
ease:Linear.easeNone, repeat:-1}, "start+=3");
function displayVals() {
//get the value from the dropdown
var singleValues = $("#single").val();
//the timeline for the changing animation
tl.to($('.s2'), 1.75, {
rotation: 360,
bezier: {
type: 'thru',
values: bezier,
curviness: singleValues
},
ease: Power1.easeInOut
});
}
displayVals();
Cycle Stagger
This pen.
.container
-(1..3).each do |i|
%div{:class => ['unit', "unit#{i}"]}
.shape
.face.bm
.face.tp
-(1..14).each do |i|
%div{:class => ['face', 'side', "s#{i}"]}
HAML
@mixin colors($max-count, $color-frequency){
$color: 360/$max-count;
@for $i from 1 through $max-count {
.s#{$i} {
background: hsl(90%, ($i - 1)*($color / $color-frequency), 40%);
}
}
}
SASS
var bP = $(".face"),
unit = $(".unit"),
tl = new TimelineLite();
tl.add("start");
tl.staggerFrom(bP, 3, {
cycle:{
backgroundColor:["#00a99d", "#0072bc", "#2e3192"],
y:[1000, -500],
x:[500, -500],
opacity:[0.9, 0.7, 0.5],
rotation: function(i) {
return i * 25
}
},
rotation: 360,
ease:Circ.easeInOut
}, 0.006, "start");
JS
Draggable
Draggable.create(features, {
edgeResistance: 0.65,
type: "x,y",
throwProps: true,
autoScroll: true
});
This pen.
Draggable
- Device-enabled for touchscreen
- Impose bounds- containing units or pixel parameters bounds:{top:100, left:0, width:1000, height:800}
- Momentum: If you have ThrowPropsPlugin you can set throwProps:true
- Draggable.hitTest() to sense if elements touch eachother
- Honors transform-origin
- Still works on transformed elements
- Lock movement to an axis lockAxis:true
- GPU-accelerated and requestAnimationFrame-driven
- more
Draggable to Control a Timeline Interaction
This pen.
Draggable to Control a Timeline Interaction
TweenMax.set($("#flowers1, #radio, #burst, #magnolia, #flowers2, #starfish, #berries1, #berries2, #berries3, #skulls, #tv, #glitch, #shadow, #lights"), {
visibility: "visible"
});
// the first scene
function sceneOne() {
var tl = new TimelineMax();
tl.add("start");
tl.staggerFromTo($f1, 0.3, {
scale: 0
}, {
scale: 1,
ease: Back.easeOut
}, 0.05, "start");
...
return tl;
}
var master = new TimelineMax({paused:true});
master.add(sceneOne(), "scene1");
Draggable to Control a Timeline Interaction
Draggable.create($gear, {
type: "rotation",
bounds: {
minRotation: 0,
maxRotation: 360
},
onDrag: function() {
master.progress((this.rotation)/360 );
}
});
- onPress
- onDragStart
- onDrag
- onDragEnd
- onRelease
- onLockAxis
- onClick
Rich Callback System and Event Dispatching
Callbacks:
"this" refers to the Draggable instance itself, so you can easily access its "target" or bounds.
If you prefer event listeners, Draggable dispatches events:
yourDraggable.addEventListener("dragend", yourFunc);
This pen.
Responsive
SVG Sprites
Start with this technique from Joe Harrison
Make it a Responsive SVG Animation Sprite
This pen.
Compare to using text with photos to illustrate an article.
8KB Gzipped.
That Whole Animation and SVG was
We can do stuff like this, All fully responsive in every direction
It doesn't neccessarily have to be fully fluid, either. Let's Implement some Responsive Design.
Like legos.
This pen.
function paintPanda() {
var tl = new TimelineMax();
tl.to(lh, 1, {
scaleY: 1.2,
rotation: -5,
transformOrigin: "50% 0",
ease: Circ.easeOut
}, "paintIt+=1");
...
return tl;
}
//create a timeline but initially pause it so that we can control it via click
var triggerPaint = new TimelineMax({
paused: true
});
...
//this button kicks off the panda painting timeline
$("#button").on("click", function(e) {
e.preventDefault();
triggerPaint.restart();
});
...
Atmosphere
Elemental Motion
- Further away is less contrast, blurry
- Does the air or environment effect movement
- Reducing precision allows for understanding
Putting Techniques Together
MorphSVG
- Tween paths to paths
- Tween shapes to paths
- Make animation magic
Point from one id to another
TweenMax.to("#start", 1, {morphSVG:{shape:"#end"},
ease:Linear.easeNone});
Use shapeIndex
TweenMax.to("#start", 1, {morphSVG:{shape:"#end",
shapeIndex:"1"}});
- Default is shapeIndex: "auto"
- Load the extra plugin, and a GUI will come up
- Usually auto will be correct, but you can pick
- Use findShapeIndex(#start, #end)
This pen.
How was this done?
MorphSVGPlugin.convertToPath("ellipse");
function flame() {
var tl = new TimelineMax();
tl.add("begin");
tl.fromTo(blurNode, 2.5, {
attr: {
stdDeviation: 9
}
}, {
attr: {
stdDeviation: 3
}
}, "begin");
var num = 9;
for (var i = 1; i <= num; i++) {
tl.to(fStable, 1, {
morphSVG: {
shape: "#f" + i
},
opacity: ((Math.random() * 0.7) + 0.7),
ease: Linear.easeNone
}, "begin+=" + i);
}
More than one way of working
By Blake Bowen
function solve(data) {
var size = data.length;
var last = size - 4;
var path = "M" + [data[0], data[1]];
for (var i = 0; i < size - 2; i +=2) {
var x0 = i ? data[i - 2] : data[0];
var y0 = i ? data[i - 1] : data[1];
var x1 = data[i + 0];
var y1 = data[i + 1];
var x2 = data[i + 2];
var y2 = data[i + 3];
var x3 = i !== last ? data[i + 4] : x2;
var y3 = i !== last ? data[i + 5] : y2;
var cp1x = (-x0 + 6 * x1 + x2) / 6;
var cp1y = (-y0 + 6 * y1 + y2) / 6;
var cp2x = (x1 + 6 * x2 - x3) / 6;
var cp2y = (y1 + 6 * y2 - y3) / 6;
path += "C" + [cp1x, cp1y, cp2x, cp2y, x2, y2];
}
return path;
}
Catmull-Rom Spline
Article about history in computer science.
var poly = document.querySelector("polyline");
var path = document.querySelector("path");
var points = [
100,350,
200,100,
300,350,
400,150,
500,350,
600,200,
700,350
];
poly.setAttribute("points", points);
path.setAttribute("d", solve(points));
var points = [
100,350,
200,150,
300,350,
400,120,
500,350,
600,180,
700,350
];
var points = [
100,350,
200,100,
300,350,
400,150,
500,350,
600,200,
700,350
];
This pen.
Accessibility
Title and associative aria tags: (WIP)
<svg aria-labelledby="title" id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 765 587">
<title id="title" lang="en">Circle of icons that illustrate Global Warming Solutions</title>
You can also add a title for elements in the SVG DOM
This resource, with support charts.
Also, this article by Dudley Storey.
viewbox as a camera
get bbox();
This pen.
Data Visualization
This pen.
FLOW CHART
var graphic = document.getElementById("graphic");
function moveChart(origin, end) {
$(origin).on("click", function() {
var moveTo = document.getElementById(end);
var s = moveTo.getBBox();
var amt = 75;
newView = "" + (s.x - amt) + " " + (s.y - amt) + " " + (s.width + amt*2) + " " + (s.height + amt*2);
TweenMax.to(graphic, 1.5, {
attr: { viewBox: newView },
ease:Circ.easeOut
});
});
}
moveChart(".start-yes", "variation");
moveChart(".start-no", "delorean");
React-motion
This pen.
This pen.
Staggering Motion
Social Coding Sites Help You Learn
Fun. Remember fun?
(I don't work for them and they don't pay me)
- Codepen
- JS Fiddle
- Dabblet
People You Should Know About
- Val Head
- Sara Soueidan
- Rachel Nabors
- Tiffany Rayside
- Chris Gannon
- CJ Gammon
- LegoMushroom
- Ana Tudor
- David Walsh Blog
- Gregor Adams
- Diaco ML
- Amelia Bellamy-Royds
- Taylor Hunt
- Dudley Storey
- GreenSock
- Blake Bowen
- I Hate Tomatoes (Petr Tichy)
- Lucas Bebber
- Rachel Smith
- Joni Trythall
- Jake Albaugh
- Louis Hoegbregts
More!
SVG Immersion Podcast and Web Animation Weekly
More Technical Information:
Frontend Masters
Advanced SVG Animation
O'Reilly Book
SVG Animation
Thank You!
These Slides:
slides.com/sdrasner/generate-sf
Intricate SVG Animations
By sdrasner
Intricate SVG Animations
Exploring many different techniques to create engaging and fun SVG animations, ranging from elemental motion to interaction.
- 5,733