0xADADA

Animating SVG with CSS

I finally got around to converting my avatar from a raster graphic to a vector format. I wanted to be able to animate the polygons using JavaScript and CSS. Another great advantage of a vector format is how the format lends itself to generate a PNG or JPG in any size. Animating SVG with CSS

I finally got around to converting my avatar from a raster graphic to a vector format. I wanted to be able to animate the polygons using JavaScript and CSS. Another great advantage of a vector format is how the format lends itself to generate a PNG or JPG in any size.

Massaging the SVG File Format

First things first, I took the source image and ran it through the delaunay triangulation - image triangulation experiment software, experimenting with various values for blur, accuracy and point count. The output of this software was a 512Kb SVG file.

Next I opened the file in Inkscape, a SVG vector graphics editor to remove the polygons in the background, cleanup some awkward polys and make a few minor adjustments. Inkscape adds a bunch of metadata and additional properties to the raw SVG that can be cleaned up.

Once I was happy with the results of my tweaking in Inkscape, I saved the SVG and opened it in SVGOMG a web-based SVG optimization GUI for the SVGO suite of tools. This tool will output a much smaller SVG file that is ready for either editing or delivery over HTTP. In this case, the file was reduced to 312Kb a 40% reduction in file size, primarily by transforming point-based <polygon> elements to the more concise to <path> element.

Animation

With the SVG file ready, I moved on to the action animation work.

The animation would be a series of CSS keyframes that would simply toggle the opacity level to produce a flickering affect. The artwork begins with all opacity set to 0 (transparent), and each keyframe incrementally toggles opacity until the last frame finally sets it to 1.

.p {
  /* each polygon "path" starts fully transparent */
  opacity: 0;
}

/* a set of keyframes that incrementally toggle opacity, this */
/* keyframes set is called "slide-into-place" */
@keyframes slide-into-place {
  0% { opacity: 1; }
  5% { opacity: 0; }
  15% { opacity: 1; }
  17% { opacity: 0; }
  50% { opacity: 1; }
  55% { opacity: 0; }
  60% { opacity: 1; }
  75% { opacity: 0.25; }
  85% { opacity: 0.85; }
  90% { opacity: 0; }
  95% { opacity: 0.25; }
  100% { opacity: 1; }
}

Every <path> in the SVG document is given a classname p (for path), I then use javascript to get references to these paths, and simply add a new classname is-animated to their class attribute. This classname will be used to attach the keyframes to the SVG paths.

.p.is-animated {
  /* sets the keyframes to use for animation */
  animation-name: slide-into-place;
  /* pause before animation starts */
  animation-delay: 1s;
  animation-timing-function: ease;
  animation-direction: normal;
  /* Run the animation only once */
  animation-iteration-count: 1;
  animation-fill-mode: forwards;
}

To attach the keyframe animation to the SVG <path>, the only think necessary is to add the is-animated classname to the element. We do this on Line-A. This is done inside a loop that iterates over all the <path> elements.

If I just add the is-animated class to the element, all polygons will flicker at the same time, looking like the entire image is flickering- which isn’t what I want. To address this, I need to randomize the start-time of when each polygon begins flickering. This makes each polygon flicker on its own timeline. I get a random number on Line-B and use that as a delay to begin the flickering.

path.style.animationDelay (on Line-C) is used to wait for a randomized timeout (Line-B) period before animation starts.

function init() {
  var paths = document.querySelectorAll('path'),
    i = 0,
    randTimeout = null;
    path = null;
  for (i = 0; i < paths.length; i++) {
    let path = paths[i];
    randTimeout = Math.floor(Math.random() * (1500 - 1) + 1);  // Line-B
    path.style.animationDelay = randTimeout + 'ms';  // Line-C
    path.classList.add('is-animated');  // Line-A
  }
}

The above init() function is called once the SVG document has finished loading.

<svg xmlns="http://www.w3.org/2000/svg"
  viewbox="0 0 660 660"
  onload="init()">

Result

(Reload the page to view the animation again)