Intricate SVG Animations

 

Sarah Drasner

 Manager, UX Design & Engineering at Trulia (Zillow)

 

@sarah_edo : twitter || @sdras : codepen

Smashing Conf, New York, June 2016

 

Sarah Drasner

Manager, UX Design and Engineering at Trulia (Zillow)

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!

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

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!

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

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:

SMashing workshop on Thursday

Advanced SVG Animation

O'Reilly Book

SVG Animation

Thank You!

Sarah Drasner

@sarah_edo on twitter

@sdras on codepen

These Slides:

slides.com/sdrasner/smashing-ny