Skip to content

Instantly share code, notes, and snippets.

@eswak
Created February 19, 2015 21:17
Show Gist options
  • Save eswak/35409a9f2ef30f35025d to your computer and use it in GitHub Desktop.
Save eswak/35409a9f2ef30f35025d to your computer and use it in GitHub Desktop.
Progressively draw SVG to create a hand drawn effect
(function closure () {
// progressively draw each <svg> without .no-draw class
Array.prototype.forEach.call(document.querySelectorAll('svg:not(.no-draw)'), function(svg) {
// animation duration & delay (default values overriden by data attributes)
var animationTimeInSeconds = Number(svg.getAttribute('data-draw-time')) || 2;
var animationStartDelay = Number(svg.getAttribute('data-draw-start-delay'))*1000 || 0;
// init, hide all svgs
var totalFrames = animationTimeInSeconds * 60;
svg.style.display = 'none';
// start to draw each <svg> after "data-draw-start-delay" (attribute) time (in seconds)
setTimeout(function() {
svg.style.display = 'inline-block';
svg.id = svg.id || Math.random().toString(36).substring(2);
// store paths in an array
var paths = Array.prototype.slice.call(document.querySelectorAll('#' + svg.id + ' path'));
// for each path
paths.forEach(function(path, i) {
var handle = 0;
var l = path.getTotalLength();
path.style.strokeDasharray = l + ' ' + l;
path.style.strokeDashoffset = l;
// start to draw them after a delay
// ex: if <svg> is composed of 2 paths, and draws in 1s,
// path 1 will start to draw immediatly and will be drawn in 500ms,
// while path 2 will be drawn between 500ms and 1s
setTimeout(function() {
drawPath(path, l, 0, totalFrames/paths.length, handle, function() {
// add .drawn class to every svg <path> that have been completely drawn
path.classList.add('drawn');
if (i === paths.length -1) {
// add .all-paths-drawn class to <svg> dom element when all
// its paths have been drawn
svg.classList.add('all-paths-drawn');
}
});
}, i*(animationTimeInSeconds/paths.length)*1000);
});
}, animationStartDelay);
});
function drawPath(path, pathLength, currentFrame, totalFrames, handle, drawn) {
var progress = currentFrame/totalFrames;
if (progress > 1) {
window.clearTimeout(handle);
path.style.strokeDashoffset = 0;
drawn && drawn();
} else {
currentFrame++;
path.style.strokeDashoffset = Math.floor(pathLength * (1 - progress));
handle = window.setTimeout(function() {
drawPath(path, pathLength, currentFrame, totalFrames, handle, drawn);
}, 1000/60);
}
}
})();
@qubodup
Copy link

qubodup commented May 4, 2018

Interesting, do you have a HTML file that uses this?

EDIT: this one works, just sometimes needs a reload due to:
draw-svg.js:19 Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Document': '#56oea3zfpwq path' is not a valid selector. at file://./draw-svg.js:19:55

<html><head></head><body>
<svg width="8cm" height="8cm" viewBox="0 0 512 512"
     xmlns="http://www.w3.org/2000/svg" version="1.1">
  <title>Example triangle01- simple example of a 'path'</title>
  <desc>A path that draws a triangle</desc>
  <path d="M 100 100 L 500 100 L 300 500 z"
        fill="orange" stroke="red" stroke-width="22" />
  <path d="M 500 500 L 100 500 L 300 100 z"
        fill="none" stroke="blue" stroke-width="33" />
</svg><script src="draw-svg.js"></script></body></html>

@nbje
Copy link

nbje commented Jul 16, 2018

I found a solution to the "not a valid selector" problem. The thing is, selectors cannot start with a digit. But as it is randomly generated, there is a chance that it will. So you can just add a prefix to avoid this. Like so:
svg.id = svg.id || "something_"+Math.random().toString(36).substring(2);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment