Complex SVG Animations with GreenSock (GSAP)

 

Sarah Drasner

 Manager, UX Design and Engineering at Trulia

 

@sarah_edo : twitter || @sdras : codepen

O'Reilly Fluent Session, March 2016

 

Sarah Drasner

Manager, UX Design and Engineering at Trulia

Why?

  • Animation is powerful to convey meaning
  • Can guide your users
  • If built correctly, animation can be useful and performant
  • Because otherwise we're not using the web to it's full potential
  • FUN!

Main Principle:

Design everything first, slowly unveil things.

This pen.

Ugly storyboards save you time!

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!

Before we get started:

Animation Performance!

Let's make some cool stuff.

We're going to use some JavaScript.

GSAP, to be exact.

here's why...

 

(I don't work for them and they don't pay me.)

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;
  }
}

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();

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

Percentage-Based Transforms on SVG!

get excited.

This pen courtesy of GreenSock.

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

We can do stuff like this, All fully responsive in every direction

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.

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 Course

Advanced SVG Animation

O'Reilly Book

SVG Animation

Thank You!

Sarah Drasner

@sarah_edo on twitter

@sdras on codepen

These Slides:

slides.com/sdrasner/fluent-session

Complex SVG Animations with GreenSock (GSAP)

By sdrasner

Complex SVG Animations with GreenSock (GSAP)

Different ways to approach design and development for SVG animation, as well as theoretical guides for working with animation in a corporate context.

  • 9,537