Skip to content

Instantly share code, notes, and snippets.

@thomaswilburn
Last active November 30, 2017 21:43
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 thomaswilburn/66ab50bd91d933cac126421cec8361a0 to your computer and use it in GitHub Desktop.
Save thomaswilburn/66ab50bd91d933cac126421cec8361a0 to your computer and use it in GitHub Desktop.
SVG demo pages
<!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>
<!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