SVG Yeah You Know Me

February 13th, 2015

In web development I sometimes get an itch to learn something new. Just a tick that encourages me to push my knowledge a bit more. The possibilities are endless and sometimes I just feel like exploring them for the sake of learning something. And this week I got the itch to teach myself some SVG animations.

SVG’s are Scalable Vector Graphics printed through a browser through a SVG code generated by programs like Adobe Illustrator or Inkscape paths. Being extremely familiar with illustrator and having no experience with Inkscape naturally I downloaded the new program, stared at it for 5 minutes and decided to return to my adobe safety zone. Nestled securely in my wheelhouse I created my illustration, making sure that all my polyfills and circles were converted to compound paths.

My company had a release of it’s new battery case for the iPhone 6, obviously a pretty big product to launch. Early in my research I’d stumbled upon this link, I was inspired by it’s application to tech: how well it described the concept of product design and engineering as process. Naturally I downloaded the git and played around with the code they’d provided. After a half-hour of finagling I realized I’d never be able to use a plugin for this technology, I’d be letting myself and my craft down and decided it was back to the grind and the hand-code method to make this happen.

So I started with some basic code to animate the SVG. I’ve always found Chris Coyier over at CSS Tricks to be a reliable source of information and his article How SVG Line Animation Works was incredibly helpful just to wrap my head around the concept. Once I understood the process behind it I was able to incorporate a simple SVG animation using jquery/javascript to manipulate the css on my path and run the animation.

function drawSVGPaths(_parentElement, _timeMin, _timeMax, _timeDelay) {
    var paths = $(_parentElement).find('path');
    $.each( paths, function(i) {
        //get the total length
        var totalLength = this.getTotalLength();
        //set PATHs to invisible
        $(this).css({
            'stroke-dashoffset': totalLength,
            'stroke-dasharray': totalLength + ' ' + totalLength
        });
        //animate
        $(this).delay(_timeDelay*i).animate({
            'stroke-dashoffset': 0
        }, {
            duration: Math.floor(Math.random() * _timeMax) + _timeMin
            ,easing: 'easeInOutQuad'
        });
    });
}
function startSVGAnimation(parentElement) {
    drawSVGPaths(parentElement, 2000, 3000, 50);
}

As you can see I’m selecting all ‘path’ elements in my _parentElement, assigning their length to a variable, then animating them over a random time interval with some clever math (I pulled the clever math from another source that I can’t seem to find, if found please let me know and I’ll link it!). This of course just ran the animation. I needed the animation to start only once the element was scrolled in to the viewport.

After a few more hours of research I found myself with the following code.

function isScrolledIntoView(elem) {
	var docViewTop = $(window).scrollTop();
	var docViewBottom = docViewTop + $(window).height();

	var elemTop = $(elem).offset().top;
	var elemBottom = elemTop + $(elem).height();

	return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
$(window).scroll(function() {
	if (isScrolledIntoView('#svg-container')) {
		startSVGAnimation($('#route'));
	}
});

This allowed me to trigger my animation once the element was scrolled into view. Solid! I started my CSS styling and jovially patted myself on the back for solving the puzzle all by my google! Until I loaded the page and realized the animation was repeating itself each time the page was scrolled, and as I scroll just a little very frequently the animation looked like it was stuttering. Obviously not my intention. Back to the code board!

I’m gonna admit this took me a lot longer to figure out than I’d like to say. I finally had to ask a developer friend to help me and the code he wrote is so annoyingly simple I wanted to punch myself for not figuring it out.

var hasExecuted = false;

$(window).scroll(function() {
	if (!hasExecuted && isScrolledIntoView('#svg-container')) {
		startSVGAnimation($('#route'));

		hasExecuted = true;
	}
});

Excuse me while I rage kick myself in the face, even looking at how simple the solution was, just sitting out of my reach still gets my blood boiling. But once I had this part refined I was able to move forward and work on fading in the actual product image on top of my SVG illustration once it was completed.

function drawSVGPaths(_parentElement, _timeMin, _timeMax, _timeDelay) {
    var paths = $(_parentElement).find('path');
    $.each( paths, function(i) {
        //get the total length
        var totalLength = this.getTotalLength();
        //set PATHs to invisible
        $(this).css({
            'stroke-dashoffset': totalLength,
            'stroke-dasharray': totalLength + ' ' + totalLength
        });
        //animate
        $(this).delay(_timeDelay*i).animate({
            'stroke-dashoffset': 0
        }, {
            duration: Math.floor(Math.random() * _timeMax) + _timeMin
            ,easing: 'easeInOutQuad'
        });
    });
    var finalImg = $(_parentElement).next('img');
    var imgTime = _timeMin + _timeMax + _timeDelay;
    setTimeout(function() {
		finalImg.animate({opacity: 1.0})
	}, imgTime);
}

My final drawSVGPaths function selected the image directly after the parent element and animated it’s opacity from 0 to 1 over the time interval defined for the animation itself. I wanted to make sure I could reuse this function on later projects, blood, sweat, and tears were going into this project I better be able to use it afterwards.

With a document.ready function to hide the paths on page load I was ready to deploy! I put this effect into use on the product page for the iPhone 6 Spare and have to say I’m pretty proud of it. I stuck through the difficulties and persevered through the frustrations. With google at my side, I can achieve anything! But mostly cool SVG animations.

Result

5t84OW

Final Code

$(document).ready(function() {
  function hideSVGPaths(_parentElement) {
    var paths = $(_parentElement).find('path');
    $.each( paths, function(i) {
    var totalLength = this.getTotalLength();
      $(this).css({
        'stroke-dashoffset': totalLength,
        'stroke-dasharray': totalLength + ' ' + totalLength
      });
    });
  }
  hideSVGPaths('#route');
  hideSVGPaths('#dimensions');
});

var hasExecuted1 = false;
var hasExecuted2 = false;

$(window).scroll(function() {
  if (!hasExecuted1 && isScrolledIntoView('#svg-container')) {
    startSVGAnimation($('#route'));

    hasExecuted1 = true;
  }
  if (!hasExecuted2 && isScrolledIntoView('#iphone6-thin')) {
    startSVGAnimation($('#dimensions'));

    hasExecuted2 = true;
  }
});

function isScrolledIntoView(elem) {
  var docViewTop = $(window).scrollTop();
  var docViewBottom = docViewTop + $(window).height();

  var elemTop = $(elem).offset().top;
  var elemBottom = elemTop + $(elem).height();

  return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}


function drawSVGPaths(_parentElement, _timeMin, _timeMax, _timeDelay) {
  var paths = $(_parentElement).find('path');
  $.each( paths, function(i) {
    //get the total length
    var totalLength = this.getTotalLength();
    //set PATHs to invisible
    $(this).css({
      'stroke-dashoffset': totalLength,
      'stroke-dasharray': totalLength + ' ' + totalLength
    });
    //animate
    $(this).delay(_timeDelay*i).animate({
      'stroke-dashoffset': 0
    }, {
      duration: Math.floor(Math.random() * _timeMax) + _timeMin
      ,easing: 'easeInOutQuad'
    });
  });
  if ($(_parentElement).next().is('img')) {
    var finalImg = $(_parentElement).next('img');
    var imgTime = _timeMin + _timeMax + _timeDelay;
    setTimeout(function() {
      finalImg.animate({opacity: 1.0})
    }, imgTime);
  }
}
function startSVGAnimation(parentElement) {
  drawSVGPaths(parentElement, 2000, 3000, 50);
}

Comment