| <!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