SVG for Complex Responsive Animation

 

Dudley Storey

 

@dudleystorey : twitter

@dudleystorey : codepen

 

Sarah Drasner

 

@sarah_edo : twitter || @sdras : codepen

 

Sarah Drasner

Consultant, Staff Writer at CSS-Tricks

 

Dudley Storey

Editor, writer, teacher, Smashing Magazine

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!

This pen.

SVG!

Why?

  • Animation is powerful to convey meaning
  • Can guide your users
  • Because otherwise we're not using the web to it's full potential
  • FUN!

UI/UX animation

Pieces of the UI move to aid in an informative UX choreography. This can typically be done with well placed CSS, some JS to trigger interactions. Responsive can be achieved with good CSS media queries.

This pen made with Sara Soueidan's circulus.

This pen.

Before we get started:

Optimize!

Before we get started:

Animation Performance!

Not all are created equal

  • Opacity
  • Transforms
  • Hardware Acceleration
@mixin accelerate($name) {
 will-change: $name;
 transform: translateZ(0);
 backface-visibility: hidden;
 perspective: 1000px;
}

.foo {
  @include accelerate(transform);
}

Case Study: Netflix

What does the "Scalable" mean?

Make a Responsive SVG Animation Sprite

Viewbox Shift with a Sprite

This pen.

Compare to using text with photos to illustrate an article.

8KB Gzipped.

That Whole Animation and SVG was

ViewBox Shift with JavaScript

var shape = document.getElementById("svg");

// media query event handler
if (matchMedia) {
        var mq = window.matchMedia("(min-width: 500px)");
        mq.addListener(WidthChange);
        WidthChange(mq);
}
// media query change
function WidthChange(mq) {
        if (mq.matches) {
            shape.setAttribute("viewBox", "0 0 490 474");
        }
        else {
            shape.setAttribute("viewBox", "0 490 500 500");
        }
};

Acts like a window to show and hide the requisite parts of the sprite

But Sarah, You Said COMPLEX

we want to do really complex stuff

OK COOL!

But we're going to need

GreenSock.

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, soon to be solved in Firefox.

More in this CSS-Tricks article.

Chrome

IE

Firefox

Safari (zoomed)

Solves Other Transform-Origin Issues

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.

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

Here's How to Build Tweens and Timelines

Scalable Graphic,

Scalable Animation

Percentage-Based Transforms on SVG!

get excited.

This pen courtesy of GreenSock.

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.

Designing Interaction in Responsive Animations

Like legos.

This pen.

//variable declaration for the global repeated animations
const gear = $("#gear1, #gear2, #gear3");
...

//animation that's repeated for all of the sections
function revolve() {
  let tl = new TimelineMax();

  tl.add("begin");
  tl.to(gear, 4, {
      transformOrigin: "50% 50%",
      rotation: 360,
      ease: Linear.easeNone
    }, "begin");
    ...

  return tl;
}

const repeat = new TimelineMax({repeat:-1});
repeat.add(revolve());
function paintPanda() {
  let 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
const triggerPaint = new TimelineMax({ paused: true });
triggerPaint.add(paintPanda());

//this button kicks off the panda painting timeline
button.addEventListener("click", function() {
  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 from GSAP

  • Tween paths to paths
  • Tween shapes to paths
  • Make animation magic
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.

Design + Information + Animation

Revisiting old approaches

Responsive animated Infographic

Conversion

(one source example, The Whole Brain Group)

  • increased traffic to their website by over 400%
  • lead increase by almost 4500%
  • the number of new visitors to their site to almost 78%

Problems

  • Not responsive- tipping point: Tim Kadlec
  • Not updated to current context
  • ^ Especially design

All posts older than 2 years.

What Happened?

This pen.

Change the viewbox in JavaScript like we did before:

Responsive:

Responsive:

Media queries for layout, and fallback with Modernizr:

/* media query example element, mobile first */
@media (max-width: 825px) {
  container {
    width: 100%;
  }
}
@media (min-width: 826px) {
  .container {
    width: 825px;
  }
}

You already know this!

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.

Motion Along a Path

This is one of the coolest things about SMIL, but the promise of support has a longer tail with GreenSock.

  • Backwards compatibility and cross browser even IE! 
  • SMIL motion along a path will probably continue to be unsupported in IE, but support for this feature will move into CSS. However, this is down the line. In the meantime, use GSAP for the widest support.
  • Side note: vote for CSS support of motion along a path in Edge here.
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");

Motion along a path: Fireflies

Motion Along a Path: Curviness

  • Can just use paths as general coordinates and smooth out the motion between
  • Either set the type parameter to soft
  • Or for more control set the type to thru (this is the default), and define a curviness value. 0 defines no curviness, 1 is normal, 2 is twice as curvy, etc

Interaction

Draggable and SVGOrigin for Responsive

TweenMax.set(cow, {
  svgOrigin:"321.05, 323.3", 
  rotation:50
});

(That whole SVG is 2KB!)

what's happening?

What the DOM actually sees

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

This pen.

tl.call(addAttr);
tl.fromTo(feTurb, 1, {
  attr: {
    baseFrequency: '0 0'
  }
}, {
  attr: {
    baseFrequency: '0.8 1.2'
  },
  ease: Sine.easeOut
});
tl.to(feTurb, 1, {
  attr: {
    baseFrequency: '0 0'
  },
  ease: Sine.easeIn
});
tl.call(removeAttr);

SVG Filters

// filter attribute helpers
function addAttr() {
  feTurb.setAttribute('baseFrequency', '0 0');
}

function removeAttr() {
  var applyFilter = document.getElementById("applyFilter");
  applyFilter.removeAttribute("filter");
}

SVG Filters

State-driven SVG Animation- Vue.js

Encapsulate what is changing

State-Driven Animation

Encapsulate what is changing- Vuex/Redux

export const store = new Vuex.Store({
  state: {
    showWeather: false,
    template: 0
  },
    mutations: {
      toggle: state => state.showWeather = !state.showWeather,
      updateTemplate: (state) => {
        state.showWeather = !state.showWeather;
        state.template = (state.template + 1) % 4;
      }
  }
});

State change can create the animation

Watchers/Observables/RxJS

<div id="app" @mousemove="coordinates">
coordinates(e) {
  ...
  this.startArms.progress(1 - (e.clientX / walleCoords)).pause();
}

HTML (Vue Component)

JavaScript

O'Reilly Book

SVG Animation

O'Reilly Book

Using SVG with CSS3 and HTML5

Thank you!

@sarah_edo on twitter

Made with Slides.com