SVG can do THAT?!

 

Sarah Drasner

 

@sarah_edo : twitter || @sdras : codepen

 

Sarah Drasner

Sr. Developer Advocate at Microsoft

SVG!

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!

Positioning in CSS got you down?

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

This pen made with Sara Soueidan's circulus.

SVG can mean conserving space in your UI until icons are needed

It doesn't neccessarily have to be fully fluid, either. Let's Implement some Responsive Design.

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

...

We can use this stability for Page Transitions

In React

  toggleShape() {
    if (this.state.screen === 0) {
      this.animFire(this.state.splitText)
    } ...
    this.setState({
      screen: (this.state.screen + 1) % 3
    })
  }
  animFire(splitText) {
    const tl = new TimelineMax,
          stA = 'start';
    
    TweenMax.set([this.g1.childNodes, this.g2.childNodes], {
      clearProps:'svgOrigin'
    })

    tl.add('start')
    tl.staggerFromTo(this.g1.childNodes, dur, {
      drawSVG: '68% 100%'
    }, {
      drawSVG: '27.75% 0%',
      ease: Back.easeOut
    }, stD, stA)
    ...
  }

In Vue.js with Nuxt

JS Hooks

export default {
  transition: {
    mode: 'out-in',
    css: false,
    enter (el, done) {

      let tl = new TimelineMax({ onComplete: done }),
          spt = new SplitText('h1', {type: 'chars' }), 
          chars = spt.chars;

      TweenMax.set(chars, {
        transformPerspective: 600,
        perspective: 300,
        transformStyle: 'preserve-3d'
      })

      tl.add('start')
      tl.from(el, 0.8, {
        scale: 0.9,
        transformOrigin: '50% 50%',
        ease: Sine.easeOut
      }, 'start')
      ...
      tl.timeScale(1.5)
    }
  ...

From Codrops

Position: fixed and morphed into pathdata:id

Use it FOR the layout

preserveAspectRatio="none"

SVG can be TINY

Before we get started:

Optimize!

Draggable for Responsive

That whole SVG is 2KB!

What's happening?

What the DOM actually sees

Entire filesize: 6KB!

Loaders!

SVG can make OTHER images smaller!

By Shaw (go follow him)

38kb => 16kb!

Make a Responsive SVG Animation Sprite

Viewbox Shift with a Sprite

This pen.

ViewBox Shift with JavaScript

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

// 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

Compare to using text with photos to illustrate an article.

8KB Gzipped.

That Whole Animation and SVG was

SVG can be accessible

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.

SVG can be styled like text

<svg class="icon" width="24" height="14" alt="Menu">
  <use xlink:href="/svg/sprites.svg#icon-hamburger"></use>
</svg>

<!-- the svg -->
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <symbol id="icon-hamburger" viewBox="0 0 24 14">
    <g fill="#FFF" fill-rule="evenodd"> 
     <!-- I guess this is the part I need to style.. 
          But I have tried everything.. 
          literally, I tried styling * {fill: red} -->
      <path d="M0 0h24v2H0zM0 6h24v2H0zM6 12h18v2H6z"/>
    </g>
  </symbol>
</svg>

The original question code

The secret: fill needs to be currentColor

SVG can BOUNCE

This pen.

data() {
  return {
    total: 200,
    radius: 15,
  }
},
methods: {
  incrementHeight() {
    this.total += 100
  },
  incrementRadius() {
    this.radius += 1
  },
  bounceBall() {   
    this.vy += this.g; // gravity increases the vertical speed
    this.x1 += this.vx; // horizontal speed inccrements horizontal position 
    this.y1 += this.vy; // vertical speed increases vertical position

    if (this.y1 > this.total - this.radius) { // if ball hits the ground
      this.y1 = this.total - this.radius; // reposition it at the ground
      this.vy *= -0.8; // then reverse and reduce its speed
    }
  },
  ...

Vue.js

animateBall() {
  //use rAF to animate but put a boundary on it so it doesn't run forever
  let start,
      vueThis = this;
  this.running = true;
  
  function step(timestamp) {
    if (!start) start = timestamp;
    var progress = timestamp - start;
    if (progress < 13000) {
      vueThis.bounceBall();
      vueThis.req = window.requestAnimationFrame(step);
    } else {
      vueThis.x1 = this.radius;
      vueThis.y1 = this.radius;
      vueThis.running = false;
    }
  }
  this.req = window.requestAnimationFrame(step);
},
<button @click="animateBall" v-if="!running">Start</button>

SVG can SNAP

ES6 Template Literals

Previously: String Concatenation

var stringie = "Hi, I ordered " + num1 + " " + 
product1 + "s and " + num2 + " " + product2 + "s.";

Hi, I ordered 5 apples and 2 oranges.

const stringie = `Hi, I ordered ${num1} 
${product1}s and ${num2} ${product2}s.`;

M two points x,y

Q two points for the bezier handle x,y,

two points x,y

function plotter(points, startX) {
  var pathArr = [],
      pathpoints = [];
  for (i = 0; i <= inc; i++) {
    pathpoints.push(points + ((i * (points*2)) + points));
    pathArr.push(` ${startX} ${pathpoints[i]}`);
  }
  return pathArr;
}

rope1.setAttribute("d", `M ${plotter(50, start1)}`.join(" L") );

Plot the points

M xcoord ycoord L xcoord ycoord

SVG can Distort

Distortion Filters

<filter id="turb">
  <feTurbulence id="turbwave" type="fractalNoise" baseFrequency="0" 
    numOctaves="2" result="turbulence_3" data-filterId="3" />
  <feDisplacementMap xChannelSelector="R" yChannelSelector="G" 
    in="SourceGraphic" in2="turbulence_3" scale="40" />
</filter>
function addFilter() {
  var all = document.getElementById("all");
  all.setAttribute("filter", "url(#turb)");
};

(function addFilterTimed() {
  window.setTimeout(addFilter, 5000);
}());

Set the filter on a timer

let turb = document.querySelectorAll('#filter-ripple-2 feImage')[0],
    feD = document.querySelectorAll('#filter-ripple-2 feDisplacementMap')[0];

function sceneOne() {
  var tl = new TimelineMax();

  tl.add('begin');
  ...
  tl.fromTo(feD, 8, {
    attr: {
      scale: 30
    }
  }, {
    attr: {
      scale: 0,
      transformOrigin: '50% 50%'
    }
  }, 'begin+=0.4');
}

Animate the filter

This pen.

This pen by Lucas Bebber (go follow him!)

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

This pen.

SVG can do on-the-fly logo adjustment

Mavo, by Lea Verou (you should go follow her, and the project)

SVG can increase KPIs

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 query example element, mobile first */
@media (max-width: 825px) {
  container {
    width: 100%;
  }
}
@media (min-width: 826px) {
  .container {
    width: 825px;
  }
}

You already know this!

SVG can draw itself

Say you have a shape:

The shape has a stroke that is dashed.

But you make the dash REALLY LONG

The dash's offset is an animatable property!

This pen

This pen by Chris Gannon (go follow him!)

You can interact with an SVG

  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);
  }
createBigCircles() {
  const svgNS = this.$refs.figure.namespaceURI;
  this.$refs.patterngroup.innerHTML = '';
  
  for (let i = 0; i < this.numLines/2; i++) {
    let circ = document.createElementNS(svgNS, 'circle');
    this.append(this.$refs.patterngroup, circ);
    this.setAttributes(circ, {
      'cx': this.size/2,
      'cy': this.size/2,
      'r': this.totesRando(this.size/2, 0),
      'fill': 'none',
      'stroke': this.gradients2[this.totesRando(1, 0)],
      'stroke-width': 1
    });
  }
},
<div class="formarea">
  <h3>Create Circles:</h3>
  <button @click="createSmCircles">Make small circles</button>
  <button @click="createBigCircles">Make big circles</button>
</div>
animation() {
  let tl = new TimelineMax()
  
  tl.add('begin')
  tl.to('line', 2, {
    rotation: 360,
    repeat: -1,
    transformOrigin: '50% 50%',
    ease: Sine.easeOut
  }, 'begin')
  ...
  
  return tl;
},
pauseAnim() {
  var tl = TimelineLite.exportRoot();
  tl.pause(0);
},
<div class="formarea">
  <h3>Animation:</h3>
  <button @click="animation">Play Animation</button>
  <button @click="pauseAnim">Stop Animation</button>
</div>

This pen.

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

HTML (Vue Component)

JavaScript

SVG can be clipped and masked

This pen.

<svg class="svg" viewBox="0 0 500 500">
  <defs>
    <mask 
      id="mask" 
      maskunits="userSpaceOnUse"
      maskcontentunits="userSpaceOnUse">
      <image 
        xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/giphy%20(24).gif" 
        width="100%" 
        height="100%" />
    </mask>
    
    ...
    
  <rect width="100%" height="100%" fill="url(#checker)" mask="url(#mask)" />
</svg>

This pen by Yoksel (go follow her!)

This reference pen by Yoksel

My CSS-Tricks article on the differences

This pen by Noel Delgado

SVG can signify something changing

This pen with Vue.js

methods: {
    //this formats the hour info without a library
    getCurrentHour(zone) {
      let newhr = new Date().toLocaleTimeString('en', {
        hour: '2-digit', 
        minute: '2-digit', 
        hour12: true, 
        timeZone: zone
      })
      return newhr
    },
  ...
}

Side note: get rid of big libraries

You can use an SVG's viewBox like a camera

get bbox();

This pen.

This pen.

FLOW CHART

SVG can make a GAME

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

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

All this, and we didn't even get to data vis!

O'Reilly Book

SVG Animation

with Val Head

🎉

Thank you!

@sarah_edo on twitter