Created
June 4, 2011 10:18
-
-
Save johan/1007786 to your computer and use it in GitHub Desktop.
Draw an SVG bit by bit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
As this is MIT licensed, I added a standard MIT license header over at my fork.
Feel free to merge it.