Skip to content

Instantly share code, notes, and snippets.

@tomhodgins
Last active January 9, 2024 15:16
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save tomhodgins/bffcc0dce83f2d593afc to your computer and use it in GitHub Desktop.
Save tomhodgins/bffcc0dce83f2d593afc to your computer and use it in GitHub Desktop.
Draw <svg> inside the browser! On mousedown and touchstart it begins drawing a line, drops nodes as you mousemove or touchmove, and then finishes the line where you mouseup or touchend. It also has support for line width and color, exporting the current drawing by email as well as downloading the drawing as an svg file.You can also import SVGs. h…
<!DOCTYPE html>
<html manifest=sketch.manifest>
<head>
<meta charset=utf-8>
<title>Sketch</title>
<meta name=apple-mobile-web-app-capable content=yes>
<meta name=apple-mobile-web-app-status-bar-style content=black>
<meta name=viewport content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, minimal-ui">
<link href="http://staticresource.com/formal.css" rel=stylesheet type=text/css>
<script>eval(unescape(escape('♶♡♲☠♩☽♤♯♣♵♭♥♮♴☮♣♲♥♡♴♥♅♬♥♭♥♮♴☨☧♣♡♮♶♡♳☧☩☬♣☽♩☮♧♥♴♃♯♮♴♥♸♴☨☧☲♤☧☩☬♦☽♤♯♣♵♭♥♮♴☮♣♲♥♡♴♥♅♬♥♭♥♮♴☨☧♬♩♮♫☧☩☬♭☽♤♯♣♵♭♥♮♴☮♣♲♥♡♴♥♅♬♥♭♥♮♴☨☧♬♩♮♫☧☩☻♩☮♷♩♤♴♨☽☲☰☰☻♩☮♨♥♩♧♨♴☽☲☰☰☻♣☮♦♩♬♬♓♴♹♬♥☽☧♬♩♧♨♴♧♯♬♤♥♮♲♯♤♹♥♬♬♯♷☧☻♣☮♢♥♧♩♮♐♡♴♨☨☩☻♣☮♭♯♶♥♔♯☨☱☰☬☰☩☻♣☮♬♩♮♥♔♯☨☱☹☰☬☰☩☻♣☮♱♵♡♤♲♡♴♩♣♃♵♲♶♥♔♯☨☲☰☰☬☰☬☲☰☰☬☱☰☩☻♣☮♬♩♮♥♔♯☨☲☰☰☬☱☹☰☩☻♣☮♱♵♡♤♲♡♴♩♣♃♵♲♶♥♔♯☨☲☰☰☬☲☰☰☬☱☹☰☬☲☰☰☩☻♣☮♬♩♮♥♔♯☨☱☰☬☲☰☰☩☻♣☮♱♵♡♤♲♡♴♩♣♃♵♲♶♥♔♯☨☰☬☲☰☰☬☰☬☱☹☰☩☻♣☮♬♩♮♥♔♯☨☰☬☱☰☩☻♣☮♱♵♡♤♲♡♴♩♣♃♵♲♶♥♔♯☨☰☬☰☬☱☰☬☰☩☻♣☮♣♬♯♳♥♐♡♴♨☨☩☻♣☮♦♩♬♬☨☩☻♣☮♳♴♲♯♫♥♓♴♹♬♥☽☧♲♥♤☧☻♣☮♬♩♮♥♗♩♤♴♨☽☳☰☻♣☮♬♩♮♥♃♡♰☽☧♲♯♵♮♤☧☻♣☮♢♥♧♩♮♐♡♴♨☨☩☻♣☮♭♯♶♥♔♯☨☴☵☬☱☳☰☩☻♣☮♢♥♺♩♥♲♃♵♲♶♥♔♯☨☴☵☬☱☳☰☬☷☰☬☲☰☰☬☱☰☵☬☱☳☰☩☻♣☮♢♥♺♩♥♲♃♵♲♶♥♔♯☨☱☰☵☬☱☳☰☬☱☴☰☬☵☰☬☱☶☵☬☱☳☰☩☻♣☮♳♴♲♯♫♥☨☩☻♣☮♣♬♯♳♥♐♡♴♨☨☩☻♣☮♳♴♲♯♫♥♓♴♹♬♥☽☧♢♬♡♣♫☧☻♣☮♬♩♮♥♃♡♰☽☧♢♵♴♴☧☻♣☮♢♥♧♩♮♐♡♴♨☨☩☻♣☮♭♯♶♥♔♯☨☱☴☰☬☲☵☩☻♣☮♬♩♮♥♔♯☨☴☰☬☱☲☵☩☻♣☮♳♴♲♯♫♥☨☩☻♣☮♦♩♬♬♓♴♹♬♥☽☧♢♬♡♣♫☧☻♣☮♢♥♧♩♮♐♡♴♨☨☩☻♣☮♭♯♶♥♔♯☨☳☰☬☱☱☴☩☻♣☮♬♩♮♥♔♯☨☳☰☬☱☴☰☩☻♣☮♬♩♮♥♔♯☨☵☱☬☱☳☵☩☻♣☮♣♬♯♳♥♐♡♴♨☨☩☻♣☮♦♩♬♬☨☩☻♦☮♴♹♰♥☽☧♩♭♡♧♥☯♸☭♩♣♯♮☧☻♦☮♲♥♬☽☧♳♨♯♲♴♣♵♴☠♩♣♯♮☧☻♭☮♲♥♬☽☧♡♰♰♬♥☭♴♯♵♣♨☭♩♣♯♮☧☻♦☮♨♲♥♦☽♭☮♨♲♥♦☽♩☮♴♯♄♡♴♡♕♒♌☨☧♩♭♡♧♥☯♰♮♧☧☩☻♤♯♣♵♭♥♮♴☮♧♥♴♅♬♥♭♥♮♴♳♂♹♔♡♧♎♡♭♥☨☧♨♥♡♤☧☩♛☰♝☮♡♰♰♥♮♤♃♨♩♬♤☨♦☩☻♤♯♣♵♭♥♮♴☮♧♥♴♅♬♥♭♥♮♴♳♂♹♔♡♧♎♡♭♥☨☧♨♥♡♤☧☩♛☰♝☮♡♰♰♥♮♤♃♨♩♬♤☨♭☩☻').replace(/u../g,'')))</script>
<style>
html, body, svg {
margin: 0;
width: 100%;
height: 100%;
background: #222;
overflow: hidden;
cursor: none;
position: fixed;
}
svg {
position: relative;
background: white;
}
nav {
display: block;
position: fixed;
bottom: 5px;
left: 5px;
}
#cursor, div {
display: block;
width: 5px;
height: 5px;
border-radius: 100%;
background: black;
position: absolute;
opacity: .5;
pointer-events: none;
transition: opacity .1s ease-in-out;
}
</style>
</head>
<svg xmlns=http://www.w3.org/2000/svg version=1.1></svg>
<nav>
<select onchange="document.getElementById('color').value=cursor.style.background=brush=this.value">
<option value=#000000 selected>Black</option>
<option value=#0000ff>Blue</option>
<option value=#008000>Green</option>
<option value=#ff0000>Red</option>
<option value=#ffd700>Yellow</option>
<option value=#ffffff>White</option>
</select>
<input id=color type=color value=#000000 style="width:75px;height:32px;" onchange="cursor.style.background=brush=this.value;">
<input id=width type=number min=1 value=5 style="width:55px" oninput="radius=this.value/2;cursor.style.width=this.value+'px';cursor.style.height=this.value+'px'">
<input type=button onclick=undo() value=undo>
<input type=button onclick=clean() value=clear>
<input type=button onclick=render() value=export>
<input type=button onclick=download() value=download>
<input id=upload type=file accept=image/svg+xml style="width:110px">
</nav>
<div id=cursor></div>
<script>
var board = document.getElementsByTagName('svg')[0],
cursor = document.getElementById('cursor'),
gesture = false,
line = '',
brush = 'black',
radius = 2.5;
if (localStorage.svg){
document.body.removeChild(board)
document.getElementsByTagName('nav')[0].insertAdjacentHTML('beforebegin', localStorage.svg)
board = document.getElementsByTagName('svg')[0]
}
// Start
board.addEventListener('mousedown',lineStart)
board.addEventListener('touchstart',lineStart)
function lineStart(e){
line = 'M'+(e.clientX||e.touches[0].clientX)+','+(e.clientY||e.touches[0].clientY)+' '
cursor.style.opacity = 1
gesture = true
e.preventDefault()
}
// Move
board.addEventListener('mousemove',lineMove)
board.addEventListener('touchmove',lineMove)
function lineMove(e){
if (gesture == true){
line += 'L'+(e.clientX||e.touches[0].clientX)+','+(e.clientY||e.touches[0].clientY)+' '
trace((e.clientX||e.touches[0].clientX),(e.clientY||e.touches[0].clientY))
}
cursor.style.top = e.clientY-radius+'px'
cursor.style.left = e.clientX-radius+'px'
}
// End
board.addEventListener('mouseup',lineEnd)
board.addEventListener('touchend',lineEnd)
function lineEnd(e){
line += 'L'+(e.clientX||e.changedTouches[0].clientX)+','+(e.clientY||e.changedTouches[0].clientY)
cursor.style.opacity = .5
var path = document.createElementNS('http://www.w3.org/2000/svg','path')
path.setAttributeNS(null,'d',line)
path.setAttributeNS(null,'fill','none')
path.setAttributeNS(null,'stroke-linecap','round')
path.setAttributeNS(null,'stroke',document.getElementById('color').value)
path.setAttributeNS(null,'stroke-width',document.getElementById('width').value)
board.appendChild(path)
board.innerHTML = board.innerHTML // force SVG repaint after DOM change
gesture = false
localStorage.svg = new XMLSerializer().serializeToString(board)
}
function undo(){
var paths = document.querySelectorAll('#board path');
board.removeChild(paths[paths.length-1])
}
function clean(){
if(window.confirm('Would you like to clear the sketch?','')===true){
var paths = board.querySelectorAll('path').length
for (i=0;i<paths;i++){
board.removeChild(board.querySelectorAll('path')[0])
}
localStorage.svg = ''
}
}
function render(){
var link = document.createElement('a'),
time = Date.now();
link.href = 'mailto:?subject=exported%20sketch&body=%3C%3Fxml%20version%3D%221.0%22%20standalone%3D%22no%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E'+encodeURIComponent(new XMLSerializer().serializeToString(board))
link.id = time
document.body.appendChild(link)
document.getElementById(time).dispatchEvent(new MouseEvent('click'))
document.getElementById(time).parentNode.removeChild(document.getElementById(time))
}
function download(){
var link = document.createElement('a'),
time = Date.now(),
svg = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'+new XMLSerializer().serializeToString(board);
link.id = time
link.setAttribute('download','sketch.svg')
link.href = 'data:text/html;charset=utf-8,' + encodeURIComponent(svg)
document.body.appendChild(link)
document.getElementById(time).dispatchEvent(new MouseEvent('click'))
document.getElementById(time).parentNode.removeChild(document.getElementById(time))
}
function trace(x,y){
var dot = document.createElement('div');
dot.style.top = y-radius+'px'
dot.style.left = x-radius+'px'
dot.style.background = brush
dot.style.width = dot.style.height = radius*2+'px'
document.body.appendChild(dot)
setTimeout(function(){dot.style.opacity=0},500)
setTimeout(function(){document.body.removeChild(dot)},1000)
}
document.getElementById('upload').addEventListener('change',importSVG)
function importSVG(e){
var file = e.target.files[0];
if (file){
var reader = new FileReader()
reader.onload = function(e){
var content = e.target.result
board.innerHTML = content
}
}
reader.readAsText(file)
}
</script>
CACHE MANIFEST
# version 2
CACHE:
sketch.html
formal.css
NETWORK:
*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment