Skip to content

Instantly share code, notes, and snippets.

@kenpenn
Last active Jan 31, 2016
Embed
What would you like to do?
styling SVG markers
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Styling SVG Markers</title>
<style id="styles">
html {
background: hsl(0, 0%, 0%);
-webkit-background-size: cover;
-moz-background-size: cover;
background-size: cover;
}
.wrap {
max-width: 860px;
margin: auto;
overflow: auto;
}
.svg-container {
width: 420px;
padding: 15px;
float: left;
}
.color-path {
fill: none;
stroke-width: 2.5px;
}
.layout-path {
fill: none;
stroke: none;
}
.marker {
stroke: none;
}
.btn-light {
cursor: pointer;
fill: url(#btn-light);
stroke: hsl(0, 0%, 60%);
stroke-width: .5px;
}
.btn-light-txt {
font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
text-anchor: middle;
fill: hsl(0, 0%, 30%);
stroke: none;
font-size: 1rem;
-webkit-user-select: none;
pointer-events: none;
}
.splainin {
font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
background : white;
border-radius: 4px;
color: hsl(0, 0%, 30%);
float: left;
font-size: .85rem;
width: 380px;
padding: 10px 12px;
margin: 40px auto;
}
.splainin a {
color: hsl(203, 100%, 50%);
text-decoration: none;
font-weight: 600;
}
.splainin ul {
margin: 0;
padding-left: 8px;
list-style: none;
}
.pi {
color: hsl(332, 98%, 51%);
}
@media only screen
and (max-width : 870px) {
.svg-container {
float: none;
display: block;
margin: auto;
}
.splainin {
float: none;
display: block;
}
}
</style>
</head>
<body>
<div class="wrap">
<div class="svg-container">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xml="http://www.w3.org/XML/1998/namespace" xml:space="preserve"
x="0" y="0" width="400px" height="440px" viewBox="0 0 500 550" >
<defs>
<marker id="marker-circle" markerWidth="10" markerHeight="10" refx="5" refy="5">
<circle cx="5" cy="5" r="3" class="marker"/>
</marker>
<marker id="marker-square" markerWidth="7" markerHeight="7" refx="4" refy="4"orient="auto">
<rect x="1" y="1" width="5" height="5" class="marker"/>
</marker>
<marker id="marker-arrow" markerWidth="12" markerHeight="12" refx="6" refy="4" orient="auto">
<path d="M 1 1 7 4 1 7 Z" class="marker"/>
</marker>
<linearGradient id="btn-light" x1="0%" y1="0%" x2="0%" y2="100%" spreadMethod="pad">
<stop stop-color="hsl(0, 0%, 90%)" offset="0"></stop>
<stop stop-color="hsl(0, 0%, 92%)" offset="0.05"></stop>
<stop stop-color="hsl(0, 0%, 97%)" offset="0.33"></stop>
<stop stop-color="hsl(0, 0%, 87%)" offset="0.67"></stop>
<stop stop-color="hsl(0, 0%, 84%)" offset="1"></stop>
</linearGradient>
</defs>
<g class="viz">
<path id="start-path" class="layout-path" d="M210,250 a40,40 0 1,0 80,0 a40,40 0 1,0 -80,0"></path>
<path id="quad-path" class="layout-path" d="M165,250 a85,85 0 1,0 170,0 a85,85 0 1,0 -170,0"></path>
<path id="mid-path" class="layout-path" d="M110,250 a140,140 0 1,0 280,0 a140,140 0 1,0 -280,0"></path>
<path id="end-path" class="layout-path" d="M10,250 a240,240 0 1,0 480,0 a240,240 0 1,0 -480,0"></path>
</g>
<g id="cycle-btn" transform="translate(186,518)">
<rect class="btn-light" x="0" y="0" rx="4" ry="4" width="128" height="32"></rect>
<text class="btn-light-txt" x="64" y="22">Cycle Colors</text>
</g>
</svg>
</div><!-- .svg-container-->
<div class="splainin">
<h2>Styling SVG Markers</h2>
<p>Using an array of color keys, this demo:</p>
<ul>
<li>appends CSS for each color to the stylesheet</li>
<li>clones svg markers for each color into the defs</li>
<li>creates a path for each color, with markers</li>
<li>cycles the CSS color class to the next path</li>
<li></li>
<li></li>
</ul>
<p><strong><em>Thanks to:</em></strong></p>
<p>O'Reilly <a href="http://shop.oreilly.com/product/9780596002237.do" target="_blank">SVG essentials</a> for the quadratic beziér curve.</p>
<p>Jacob Jenkov for <a href="http://tutorials.jenkov.com/svg/marker-element.html" target="_blank">SVG markers</a>.</p>
<p>David Walsh for <a href="http://davidwalsh.name/add-rules-stylesheets" target="_blank">adding styles</a>.</p>
<p>complexdan for <a href="http://complexdan.com/svg-circleellipse-to-path-converter/" target="_blank">SVG circles to paths</a>.</p>
<p><a href="https://plus.google.com/+PaulIrish/posts" target="_blank"><span class="pi">Paul Irish</span></a> for <a href="http://mothereffinghsl.com/" target="_blank"><span class="mef">m</span><span class="mef">o</span><span class="mef">t</span><span class="mef">h</span><span class="mef">e</span><span class="mef">r</span><span class="mef">e</span><span class="mef">f</span><span class="mef">f</span><span class="mef">i</span><span class="mef">n</span><span class="mef">'</span> <span class="mef">h</span><span class="mef">s</span><span class="mef">l</span></a>.</p>
</div>
</div>
<script>
window.addEventListener('DOMContentLoaded', function () {
"use strict";
// rainbow starting with blue
var colors = ['hsl-242', 'hsl-259', 'hsl-273', 'hsl-296', 'hsl-341',
'hsl-359', 'hsl-18', 'hsl-35', 'hsl-52', 'hsl-83',
'hsl-127', 'hsl-160', 'hsl-190', 'hsl-212', 'hsl-227'];
// add styles to the page for colors and SVG paths
var addStyles = function () {
// select the <style> tag
var styles = document.querySelector('#styles');
// select the <style> tag's CSSStyleSheet
var styleSheet = styles.sheet;
// create styles for the colors, the paths, and the markers
colors.forEach(function (color) {
var hslColor = color.replace( '-', '(' ) + ', 100%, 50%)';
var colorStyle = '.' + color + ' { stroke: ' + hslColor + '; fill: ' + hslColor + '; color: ' + hslColor+ '; }';
var pathStyle = '.color-path.' + color + ' { ' +
'marker-start: url(#marker-circle-' + color + '); ' +
'marker-mid: url(#marker-square-' + color + '); ' +
'marker-end: url(#marker-arrow-' + color + '); ' +
'; }';
// index 0 to add to the front of the CSSRuleList,
// to avoid adding !important to the styles already present
styleSheet.insertRule(pathStyle, 0);
styleSheet.insertRule(colorStyle, 0);
});
};
// create def elements for each color and marker type; append to defs
var addDefs = function () {
var defs = document.querySelector('defs');
// add the appropriate id to the defs element, add the appropriate class to its marker
var colorDef = function (def, color) {
// add a color class to the def element's marker child
var marker = def.querySelector('.marker');
marker.classList.add(color);
// set the appropriate id on the def element
def.id = def.id + '-' + color;
};
colors.forEach(function (color) {
// for each color, select and clone the def element for each marker
var defArray = [];
defArray.push(defs.querySelector('#marker-circle').cloneNode(true));
defArray.push(defs.querySelector('#marker-square').cloneNode(true));
defArray.push(defs.querySelector('#marker-arrow').cloneNode(true));
defArray.forEach(function (def) {
colorDef(def, color);
defs.appendChild(def);
});
});
};
// create def elements for each color and marker type; append to defs
var addMefs = function () {
var forEach = Array.prototype.forEach;
var mefs = document.querySelectorAll('span.mef');
colors.forEach(function ( color, cx ) {
if ( mefs[cx] ) {
mefs[cx].classList.add(color);
}
});
};
// create arrays of x,y objects for the start, middle and end of paths
var crtCtrlPts = function (markerPos) {
var ctrlArr = [];
var ctrlPath = document.querySelector('#' + markerPos + '-path')
var getPts = function (path) {
var pathLen = path.getTotalLength();
var pathPart = 1 / colors.length;
colors.forEach(function (color, cx) {
var pathTo = pathLen * ( pathPart * (cx + 1) );
if ( markerPos === 'quad' ) { pathTo -= 25; }
var gp = ctrlPath.getPointAtLength( pathTo );
var pt = { x: Math.round(gp.x), y: Math.round(gp.y) };
ctrlArr.push(pt);
});
};
getPts(ctrlPath);
return ctrlArr.reverse();
};
// control points for paths
var ctrlPts = {
start : crtCtrlPts('start'),
quad : crtCtrlPts('quad'),
mid : crtCtrlPts('mid'),
end : crtCtrlPts('end')
};
var crtPaths = function () {
var viz = document.querySelector('.viz');
var addPath = function(color, cx) {
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
var pts = {};
var dString;
var posArr = Object.keys(ctrlPts);
posArr.forEach(function (pos) {
var pt = ctrlPts[pos][cx];
pts[pos] = pt;
});
path.id = 'qp-' + cx;
dString = 'M ' + pts.start.x + ' ' + pts.start.y +
' Q ' + pts.quad.x + ' ' + pts.quad.y +
', ' + pts.mid.x + ' ' + pts.mid.y +
' T ' + pts.end.x + ' ' + pts.end.y;
path.setAttribute( 'd', dString );
path.classList.add( 'color-path', color );
viz.appendChild(path);
};
colors.forEach(function ( color, cx ) { addPath( color, cx ); });
};
var forEach = Array.prototype.forEach;
var colorsLength = colors.length;
var cycleTo;
var cycleMs = 50;
var cycleColors = function ( paths, mefs ) {
var nextColor = function(colorClass) {
var colorX = colors.indexOf(colorClass);
colorX -= 1;
if ( colorX < 0 ) { colorX = colors.length -1; }
return colors[colorX];
};
forEach.call( paths, function ( path ) {
var colorClass = path.getAttribute('class')
.replace('color-path ', '');
var nextHsl = nextColor(colorClass);
path.setAttribute('class', 'color-path ' + nextHsl);
});
forEach.call( mefs, function ( mef ) {
var colorClass = mef.className.replace('mef ', '');
var nextHsl = nextColor(colorClass);
mef.className = 'mef ' + nextHsl;
});
cycleTo = setTimeout(function () {
cycleColors( paths, mefs );
}, cycleMs );
};
document.querySelector('#cycle-btn').addEventListener('click', function(evt) {
var cycleTxt = document.querySelector('.btn-light-txt');
var cycleStr = cycleTxt.innerHTML;
var paths, mefs;
evt.stopPropagation();
if ( cycleStr === 'Stop' ) {
clearTimeout(cycleTo);
cycleTxt.innerHTML = 'Cycle Colors';
} else {
paths = document.querySelectorAll('.viz .color-path');
mefs = document.querySelectorAll('span.mef');
cycleTxt.innerHTML = 'Stop';
cycleTo = setTimeout(function () {
cycleColors( paths, mefs );
}, cycleMs );
}
});
addStyles();
addDefs();
addMefs()
crtPaths();
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment