Innovative SVG Animations
Smashing Conf Freiburg
Sarah Drasner
This pen.
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
- Using the web to its full potential
- Fun!
SVG!
This pen.
Flash is dead! Long live flash!
- Interactive, immersive
- Narrative
- UI/UX Animations
- Prototyping
- Data Visualizations
SVG ANIMATION:
What is it good for?
Accessibility
Title and associative aria tags:
<svg aria-labelledby="title"
id="svg"
role="presentation"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 765 587">
<title id="title"
lang="en">
Icons that illustrate Global Warming Solutions
</title>
Title for elements in the SVG DOM
Role to let the screen reader know whether to traverse
This resource, with support charts.
This article by Heather Migliorisi.
Before we get started:
Optimize!
illustrator: export as
(not save as)
Not all are created equal
- Opacity
- Transforms
This pen.
Narrative
From this CSS-Tricks Article
This pen.
Main Principle:
Design everything first, slowly unveil things.
Ugly storyboards save you time!
Tools!
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
- D3.js was built for data vis but you can do a lot more with it
This pen.
React-Motion
<StaggeredMotion
defaultStyles = {arr}
styles={this.getStyles}>
{lines =>
<div className="demo">
{lines.map(({x, y, rotate}, i) =>
<div
key={i}
className={`playthings s${i}`}
// we have to subtract half the amount of $amt in the
//CSS panel so that the mouse stays in the center of
//the object we're creating
style={{
WebkitTransform: `translate3d(${x - amtHalf}px, ${y - amtHalf}px, 0) rotate(${rotate}deg)`,
transform: `translate3d(${x - amtHalf}px, ${y - amtHalf}px, 0) rotate(${rotate}deg)`
}} />
)}
</div>
}
</StaggeredMotion>
Let's make some cool stuff.
GreenSock/GSAP
This pen.
How did we use SVG?
Three ways:
- Directly inline in the HTML, for loops
- Background images, for things like taco
- Inline in React
const App = React.createClass({
getInitialState: function() {
return {
score: 500,
startTime: 0,
finalScore: 0
};
},
_startGame: function(setGameToStart) {
this.setState({ isPlaying: setGameToStart,
score: 500,
startTime: Date.now()
});
},
_updateScore: function(scoreDelta) {
let score = Math.min(Math.max(0, this.state.score + scoreDelta), 1270);
this.setState({ score: score });
if (score === 0 || score === 1270) {
this.setState({ isPlaying: false,
finalScore: 10000 - Math.round((Date.now() - this.state.startTime) / 30) });
}
},
...
render() {
return (
<div>
...
<HeartMeter score={this.state.score}/>
...
</div>
)
}
});
const HeartMeter = React.createClass({
render() {
return (
<div>
<svg className="heartmeter" xmlns="http://www.w3.org/2000/svg" width="250" height="50" viewBox="0 0 1741.8 395.6">
<path d="M1741.8 197.7c0 109.3-89 197.8-198.8 197.8a198.6 198.6 0 0 1-158.5-78.4H11.2A11.2 11.2 0 0 1 0 305.9V89.5a11.2 11.2 0 0 1 11.2-11.1h1373.4A198.8 198.8 0 0 1 1543 0c109.8 0 198.8 88.5 198.8 197.7z" fill="#000"/>
<path d="M1591.8 127c-18.3 0-34.1 14.8-41.4 30.3-7.3-15.5-23.1-30.3-41.4-30.3a45.7 45.7 0 0 0-45.7 45.5c0 51.1 51.8 64.5 87.1 115.1 33.4-50.2 87.1-65.6 87.1-115.1a45.7 45.7 0 0 0-45.7-45.5z" fill="#b29968"/>
<rect x="68.2" y="140.8" width={this.props.score} height="101.55" fill="#9391aa"/>
</svg>
</div>
)
}
});
How did we animate?
FOUR ways:
- GSAP looping in functions outside React
- RAF to detect collisions
- GSAP for keypress events
- Repeating callbacks for tacos and margaritas
Request Animation Frame
(function getCoords() {
let tCoords = tC1.getBoundingClientRect(),
txCoords = txC1.getBoundingClientRect(),
mCoords = mC1.getBoundingClientRect(),
elCoords = ele1.getBoundingClientRect();
function intersectRect(a, b) {
return Math.max(a.left, b.left + 40) < Math.min(a.right, b.right - 40) &&
Math.max(a.top, b.top + 40) < Math.min(a.bottom, b.bottom - 40);
}
// can't do if/else because sometimes they both come out at once
// and one of them will be ignored
if (intersectRect(tCoords, elCoords)) {
getHitTestIncrease();
}
if (intersectRect(txCoords, elCoords)) {
getHitTestDecrease();
}
if (intersectRect(mCoords, elCoords)) {
getHitTestMargarita();
}
requestAnimationFrame(getCoords);
}());
request animation frame
- Use instead of setInterval/setTimeout
- polyfill on NPM
- declarative- you don't decide the time delta
- before drawing next frame, execute logic
- maximize perf for inactive tabs- relieves resources
- js-stroll - learn what's happening under the hood
Bubble.prototype = {
init: function (x, y, vx, vy) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
},
update: function (dt) {
// friction opposes the direction of velocity
var acceleration = -Math.sign(this.vx) * friction;
// distance = velocity * time + 0.5 * acceleration * (time ^ 2)
this.x += this.vx * dt + 0.5 * acceleration * (dt ^ 2);
this.y += this.vy * dt + 0.5 * gravity * (dt ^ 2);
// velocity + acceleration * time
this.vy += gravity * dt;
this.vx += acceleration * dt;
this.circ.setAttribute("cx", this.x);
this.circ.setAttribute("cy", this.y);
this.circ.setAttribute("stroke", "rgba(1,146,190," + this.opacity + ")");
}
};
BT Dubs, this is d3 under the hood
(function animate(currentTime) {
var dt;
requestAnimationFrame(animate);
...
for (var i = 0; i < particles.length; i++) {
particles[i].update(dt);
...
}
}());
GSAP for Animation
_flyBy: function(el, amt, name, delay) {
if (this.props._isGameOver()) return;
...
TweenMax.fromTo(el, amt, {
rotation:0,
y:randY,
x:window.innerWidth + 200
}, {
x: -200,
y:randY,
rotation: 360,
delay: delay,
onComplete:this._flyBy,
onCompleteParams:[el, amt, name, delay],
ease: Power1.easeInOut
});
},
This pen.
syntax
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.
_hitTestIncrease: function() {
if (!this.state.tacoPassed && this.props.isPlaying) {
//animation for wowie
let inWow = this.refs.inWow,
tl = new TimelineLite();
audioIncrease.play();
tl.fromTo(inWow, 0.4, {
autoAlpha: 0,
scale: 0.5
}, {
autoAlpha: 1,
scale: 1,
ease: Power4.easeOut
});
tl.to(inWow, 0.2, {
autoAlpha: 0,
scale: 0.5,
ease: Power2.easeIn
}, "+=0.3");
this.setState({ tacoPassed: true });
this.props._updateScore(75);
}
},
Why didn't we use React-Motion?
- More verbose
- There is no way to write a loop without writing an infinite loop
One more gotcha:
- Staggers can't create arrays with new number of objects
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
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
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.
Draggable
Draggable to Control a Timeline Interaction
Draggable.create($gear, {
type: "rotation",
bounds: {
minRotation: 0,
maxRotation: 360
},
onDrag: function() {
master.progress((this.rotation)/360 );
}
});
Draggable.create(features, {
edgeResistance: 0.65,
type: "x,y",
throwProps: true,
autoScroll: true
});
This pen.
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.
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");
//balloon
function balloonGrow(){
var balloon = $("#balloon")[0];
var radius = balloon.getAttribute("r");
var cySet = balloon.getAttribute("cy");
balloon.setAttribute('r', parseInt(radius) + 10);
balloon.setAttribute('cy', parseInt(cySet) - 10);
if (parseInt(radius) > 125) {
ion.sound.play("balloon-pop3");
var balloonTl = new TimelineMax();
balloonTl.add("pop");
balloonTl.to("#balloon", 0.05, {
scale: 5,
opacity: 0,
transformOrigin: "50% 50%",
ease: Circ.easeOut
}, "pop");
...
setTimeout(function(){
balloon.setAttribute("r", "45");
balloon.setAttribute("cy", "233.5");
}, 200);
}
}
//desktop keyboard
$(document).keydown(function(e) {
switch(e.which) {
case 70: // f
//balloon
balloonGrow();
ion.sound.play("hit4");
break;
...
default: return; // exit this handler for other keys
}
e.preventDefault(); // prevent scrolling with arrows
});
Touch on Mobile
This pen.
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:
Val head and I
Web animation workshops!
Launching this week!
🎉
O'Reilly Book
SVG Animation
Thank You!
These Slides:
slides.com/sdrasner/innovative
Innovative SVG Animations
By sdrasner
Innovative SVG Animations
Exploring many different techniques to create engaging and fun SVG animations, ranging from elemental motion to interaction.
- 5,999