Last active
November 30, 2017 21:43
-
-
Save thomaswilburn/66ab50bd91d933cac126421cec8361a0 to your computer and use it in GitHub Desktop.
SVG demo pages
This file contains 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
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Generating SVG from vanilla JS</title> | |
</head> | |
<body> | |
<svg class="target-element"></svg> | |
<script> | |
// all XML elements must be created with a namespace | |
var NS = "http://www.w3.org/2000/svg"; | |
// m() is our version of hyperscript's h() | |
var m = function(tag, attrs = {}, contents = []) { | |
var element = document.createElementNS(NS, tag); | |
if (attrs) for (var k in attrs) { | |
element.setAttribute(k, attrs[k]); | |
} | |
if (typeof contents == "string") { | |
element.textContent = contents; | |
} else { | |
contents.forEach(c => element.appendChild(c)); | |
} | |
return element; | |
}; | |
var target = document.querySelector(".target-element"); | |
// we'll start by generating some basic markup | |
// it looks very similar to the output, really | |
var contents = | |
// <g> | |
m("g", null, [ | |
// <rect x=10 y=10 width=50 height=50 fill=wheat /> | |
m("rect", { x: 10, y: 10, width: 50, height: 50, fill: "wheat" }), | |
// <text x=20 y=20 fill=teal>Hello, world!</text> | |
m("text", { x: 20, y: 20, fill: "teal" }, "Hello, world!"), | |
// <g> | |
m("g", null, [ | |
// <circle cx=50 cy=50 r=20 fill=coral /> | |
m("circle", { cx: 50, cy: 50, r: 20, fill: "coral" }) | |
// </g> | |
]) | |
// </g> | |
]); | |
target.appendChild(contents); | |
// generating a star is actually really easy: | |
// walk around a circle with n*2 points, alternating between near/far | |
var makeStar = function(points) { | |
var stops = points * 2; | |
var starPath = []; | |
for (var i = 0; i < stops; i++) { | |
// in or out? | |
var distance = i % 2 ? 10 : 5; | |
// convert to radians around a circle | |
var angle = i / stops * Math.PI * 2 + Math.PI * .5; | |
starPath[i] = [Math.cos(angle) * distance, Math.sin(angle) * distance]; | |
} | |
// path syntax: | |
// Mx,y - Move the pen to x,y | |
// Lx,y - Draw a line to x,y | |
// Z - close the path | |
var pathString = starPath.map((p, i) => (i ? "L" : "M") + p.join(",")).join(" ") + "Z"; | |
return pathString; | |
}; | |
// now we can use array maps to create the children for this group | |
var starContainer = m("g", { | |
transform: "translate(50 50)" | |
}, new Array(5).fill(0).map(function(_, i) { | |
return m("path", { | |
d: makeStar(i + 5), | |
transform: `translate(${i * 20} 0)`, | |
fill: "rebeccapurple" | |
}) | |
})); | |
target.appendChild(starContainer); | |
</script> | |
</body> | |
</html> |
This file contains 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
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Vue + SVG</title> | |
</head> | |
<body> | |
<div class="vue-container"> | |
<svg-target></svg-target> | |
</div> | |
<script type="text/html" class="vue-template"> | |
<div class="vue-demo"> | |
<div class="svg-container" :class="orientation"> | |
<svg | |
:viewBox="viewBox" | |
:preserveAspectRatio="camera.aspect"> | |
<g v-for="g in elements" v-on:click="goto(g)"> | |
<rect :x="g.x" :y="g.y" width="10" height="10" :fill="g.color"></rect> | |
<circle :cx="g.x + 5" :cy="g.y + 5" width="4" r="1" fill="white"></circle> | |
</g> | |
</svg> | |
</div> | |
<button v-on:click="preset(true)" :disabled="animation">Zoom in</button> | |
<button v-on:click="preset(false)" :disabled="animation">Zoom out</button> | |
<select v-model="camera.aspect"> | |
<option>none</option> | |
<option>xMinYMin meet</option> | |
<option>xMidYMid meet</option> | |
<option>xMaxYMax meet</option> | |
<option>xMinYMin slice</option> | |
<option>xMidYMid slice</option> | |
<option>xMaxYMax slice</option> | |
</select> | |
<select v-model="orientation"> | |
<option>portrait</option> | |
<option>landscape</option> | |
</select> | |
</div> | |
</script> | |
<script src="https://unpkg.com/vue"></script> | |
<script> | |
var rgb = (r, g, b) => `rgb(${r | 0}, ${g | 0}, ${b | 0})`; | |
Vue.component("svg-target", { | |
template: document.querySelector(".vue-template").innerHTML, | |
data: () => ({ | |
camera: { | |
x: 0, | |
y: 0, | |
width: 100, | |
height: 150, | |
aspect: "xMidYMid meet" | |
}, | |
orientation: "portrait", | |
animation: false, | |
elements: new Array(150).fill(0).map(function(_, index) { | |
return { | |
x: (index % 10) * 10, | |
y: Math.floor(index / 10) * 10, | |
color: rgb(Math.random() * 128, Math.random() * 128, Math.random() * 128) | |
} | |
}) | |
}), | |
computed: { | |
viewBox() { | |
var { x, y, width, height } = this.camera; | |
return [x, y, width, height].join(" "); | |
} | |
}, | |
methods: { | |
preset(inward) { | |
var to = inward ? | |
{ x: 40, y: 40, width: 20, height: 20 } : | |
{ x: 0, y: 0, width: 100, height: 150 }; | |
this.zoom(to); | |
}, | |
zoom(to) { | |
if (this.animation) return; | |
this.animation = true; | |
var start = performance.now(); | |
var duration = 1000; | |
var from = { | |
x: this.camera.x, | |
y: this.camera.y, | |
width: this.camera.width, | |
height: this.camera.height | |
}; | |
var tick = t => { | |
var elapsed = t - start; | |
var delta = elapsed / duration; | |
if (delta >= 1) delta = 1; | |
for (var k in from) { | |
var f = from[k]; | |
var t = to[k]; | |
this.camera[k] = f + (t - f) * delta; | |
} | |
if (delta == 1) return this.animation = false; | |
requestAnimationFrame(tick); | |
}; | |
requestAnimationFrame(tick); | |
}, | |
goto(g) { | |
var target = { | |
x: g.x, | |
y: g.y, | |
width: 10, | |
height: 10 | |
}; | |
this.zoom(target); | |
} | |
} | |
}); | |
var vue = new Vue({ el: document.querySelector(".vue-container") }); | |
</script> | |
<style> | |
.svg-container { | |
width: 50vw; | |
border: 1px dotted salmon; | |
margin: auto; | |
position: relative; | |
} | |
.svg-container.portrait::before { | |
padding-bottom: 120%; | |
content: ""; | |
display: block; | |
} | |
.svg-container.landscape::before { | |
padding-bottom: 60%; | |
content: ""; | |
display: block; | |
} | |
.svg-container svg { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
} | |
</style> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment