Skip to content

Instantly share code, notes, and snippets.

@nimbupani
Forked from johan/draw-svg.js
Created June 7, 2011 20:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nimbupani/1013086 to your computer and use it in GitHub Desktop.
Save nimbupani/1013086 to your computer and use it in GitHub Desktop.
Draw an SVG bit by bit
var steps = 0 // steps drawn on the current element, so far
, info = document.getElementById('next_step')
, svgs = [].slice.call(document.getElementsByTagName('svg'))
, svg, walker, current;
function lexSVGPath(d) {
function command(seg) {
var cmd = seg.match(/[a-z]/i), arg, cnt;
if (cmd) {
cmd = cmd[0]; // which subcommand
cnt = arg_cnt[cmd.toLowerCase()];
arg = seg.match(numbers) || [];
lexemes.push([cmd].concat(arg.splice(0, cnt)));
if (cnt)
while (arg.length)
lexemes.push(arg.splice(0, cnt));
}
}
var numbers = /[+-]?(\d+(\.\d+(e\d+(\.\d+)?)?)?|\.\d+(e\d+(\.\d+)?)?)/gi
, arg_cnt = { m: 2, z: 0, l: 2, h: 1, v: 1, s: 4, c: 6, q: 4, t: 2 }
, lexemes = [];
d.split(/(?=[a-z])/i).forEach(command);
return lexemes;
}
function pathSegments(path, n) {
function cmd(c) {
return /[a-z]/i.test(c[0]) ? c[0] + c.slice(1).join(' ') : c.join(' ');
}
if ('string' === typeof path)
path = lexSVGPath(path);
path = path.slice(0, n);
return path.map(cmd).join(' ');
}
function next(node) {
return ({ g: NodeFilter.FILTER_SKIP
, path: NodeFilter.FILTER_ACCEPT
, circle: NodeFilter.FILTER_ACCEPT
})[(node.nodeName||'').toLowerCase()] || NodeFilter.FILTER_REJECT;
}
function step(button) {
var d, colour, styles, path, left = step.left;
if (!current) {
svg = svgs[svg ? (svgs.indexOf(svg) + 1) % svgs.length : 0];
walker = document.createTreeWalker(svg, NodeFilter.SHOW_ELEMENT, next, !!0);
left = 0;
// unfill all paths, hide all non-paths, save colours and path descriptions
while ((current = walker.nextNode())) {
++left;
if ((d = current.getAttribute('d'))) {
current._d = d;
styles = getComputedStyle(current, '');
current._c = 'none' === styles.fill ? styles.stroke : styles.fill;
current.style.fill = 'none';
}
else
current.style.display = 'none';
}
button.title = button._t = pluralize(step.left = left, 'item') +' left';
// reset the walker and move to the first element
walker.currentNode = walker.root;
current = walker.nextNode();
steps = 0;
}
// not a path? just make it show up, and proceed to the next node
if (!(d = current._d)) {
current.style.display = '';
current = walker.nextNode();
button.title = button._t = pluralize(--step.left, 'item') +' left';
return;
}
// set the stroke to the element's fill colour and break the path into atoms
current.style.stroke = current._c;
path = lexSVGPath(current._d);
// if we're not finished with all steps, just draw up to the next step
if (++steps <= path.length)
current.setAttribute('d', pathSegments(path, steps));
else { // all done; fill it and proceed to the next image sub-element
current.style.fill = '';
current.style.stroke = '';
current = walker.nextNode();
button.title = button._t = pluralize(--step.left, 'item') +' left';
steps = 0;
}
button.title = button._t + '; sub-step '+ steps +'/'+ path.length;
// completed?
if (!current) {
button.textContent = 'Done!';
explain('Click again to repaint the next SVG image!');
}
else if (steps && steps === path.length)
explain(button.textContent = 'Fill this SVG path element');
else {
button.textContent = 'Draw next segment:';
explain();
}
}
function pluralize(n, name) {
return n +' '+ name + (n === 1 ? '' : 's');
}
function explain(msg) {
if (!msg) {
var path = lexSVGPath(current._d)
, next = pathSegments(path, steps + 1)
, what = next.replace(/^.*([a-z])[^a-z]*$/i, '$1')
, type = what.toLowerCase()
, args = path[steps].join(' ').replace(/^[a-z]/, '')
, desc =({ m: 'move to'
, z: 'close path'
, l: 'line to'
, h: 'horizontal line to'
, v: 'vertical line to'
, s: 'smooth curve to'
, c: 'curve to'
, q: 'quadratic Bézier curve to'
, t: 'smooth quadratic Bézier curve to'
})[type]
, pref = type == 'z' || type == what ? '' : 'absolute ';
msg = pref + desc +' = '+ what + args;
}
info.textContent = msg;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment