Javascript utils for other pens
A Pen by Andreas Borgen on CodePen.
Javascript utils for other pens
A Pen by Andreas Borgen on CodePen.
| <h2>Javascript utils for other pens</h2> | |
| <h3>CSS examples:</h3> | |
| <label class="check-switch"> | |
| Checkbox "label.check-switch": | |
| <input type="checkbox" checked /> | |
| <span class="knob"></span> | |
| </label> |
| "use strict"; | |
| var ABOUtils = ABOUtils || {}; | |
| (function(utils, undefined) { | |
| utils.dropFile = function(target, callback, options) { | |
| options = options || {}; | |
| var fileUrl, | |
| acceptedTypes = options.acceptedTypes; | |
| function handleFile(file) { | |
| if(!file) { return; } | |
| if(acceptedTypes && (acceptedTypes.indexOf(file.type) < 0)) { return; } | |
| //New and better(?) way.. | |
| //https://developer.mozilla.org/en-US/docs/Web/API/Camera_API/Introduction | |
| //http://stackoverflow.com/questions/31742072/filereader-vs-window-url-createobjecturl | |
| // | |
| // var reader = new FileReader(); | |
| // reader.onload = function (event) { | |
| // //console.log('ABOUtils.dropImage, read', event); | |
| // callback(event.target.result); | |
| // }; | |
| // reader.readAsDataURL(file); | |
| // | |
| if(fileUrl) { | |
| //We probably don't need to hang on to the previous file anymore, | |
| //so we release it for performance reasons: | |
| URL.revokeObjectURL(fileUrl); | |
| //console.log('AFD Revoked', fileUrl); | |
| } | |
| fileUrl = URL.createObjectURL(file); | |
| //console.log('AFD Created', fileUrl); | |
| callback(fileUrl, file); | |
| } | |
| //If we are intercepting a file input field, we use the onchange event instead of drag/drop events. | |
| //That way we fetch the file both on drag/drop (built-in behavior for file input fields), | |
| //and when a file is selected through the old-fashioned "Browse" button. | |
| // | |
| //http://stackoverflow.com/questions/4459379/preview-an-image-before-it-is-uploaded | |
| if((target.nodeName === 'INPUT') && (target.type === 'file')) { | |
| target.onchange = function(e) { | |
| var input = e.target; | |
| if (input.files) { | |
| handleFile(input.files[0]); | |
| } | |
| }; | |
| } | |
| else { | |
| //http://html5demos.com/dnd-upload | |
| target.ondragover = function () { return false; }; | |
| target.ondragend = function () { return false; }; | |
| target.ondrop = function (e) { | |
| e.preventDefault(); | |
| var file = e.dataTransfer.files[0]; | |
| //console.log('ABOUtils.dropFile, dropped', file.type); | |
| handleFile(file); | |
| } | |
| } | |
| }; | |
| utils.dropImage = function(target, callback) { | |
| utils.dropFile(target, callback, { | |
| acceptedTypes: ['image/png', | |
| 'image/jpeg', | |
| 'image/gif', | |
| 'image/svg', | |
| 'image/svg+xml'] | |
| }); | |
| }; | |
| utils.initArray = function(config) { | |
| var len = config.length, | |
| fac = config.factory || function() { return config.value; }; | |
| //"RangeError: Maximum call stack size exceeded" on large arrays (length ~250000) | |
| // | |
| // //http://stackoverflow.com/questions/1295584/most-efficient-way-to-create-a-zero-filled-javascript-array | |
| // return Array.apply(null, Array(len)) | |
| // .map(function(x, i) { return fac(i); }); | |
| // | |
| var array = []; | |
| for(var i = 0; i < len; i++) { | |
| array.push(fac(i)); | |
| } | |
| return array; | |
| } | |
| //http://stackoverflow.com/questions/7459704/in-javascript-best-way-to-convert-nodelist-to-array | |
| //Usage: | |
| // ABOUtils.live('click', 'nav .aap a', function(event) { console.log(event); alert(this + ' clicked'); }); | |
| utils.live = function(eventType, elementQuerySelector, callback) { | |
| document.addEventListener(eventType, function (event) { | |
| var qs = $$(elementQuerySelector); | |
| if (qs && qs.length) { | |
| var el = event.target, index = -1; | |
| while (el && ((index = qs.indexOf(el)) === -1)) { | |
| el = el.parentElement; | |
| } | |
| if (index > -1) { | |
| callback.call(el, event); | |
| } | |
| } | |
| }); | |
| } | |
| //http://stackoverflow.com/a/27761213/1869660 | |
| utils.mapTouchToMouse = function(target) { | |
| //MouseEvent() constructor polyfill: | |
| //https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent | |
| (function (window) { | |
| try { | |
| new MouseEvent('click'); | |
| } catch (e) { | |
| return false; // No need to polyfill | |
| } | |
| console.log('No MouseEvent constructor. Applying polyfill.'); | |
| function MouseEvent(eventType, params) { | |
| params = params || { bubbles: false, cancelable: false }; | |
| //https://developer.mozilla.org/en-US/docs/Web/API/Document/createEvent#Notes | |
| var mouseEvent = document.createEvent('MouseEvents'); | |
| mouseEvent.initMouseEvent( | |
| //type, canBubble, cancelable, view, | |
| eventType, params.bubbles, params.cancelable, window, | |
| //detail, screenX, screenY, clientX, clientY, | |
| 0, params.screenX, params.screenY, params.clientX, params.clientY, | |
| //ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget | |
| false, false, false, false, (params.button || 0), null); | |
| return mouseEvent; | |
| } | |
| MouseEvent.prototype = Event.prototype; | |
| window.MouseEvent = MouseEvent; | |
| })(window); | |
| function createMouseEvent(type, touchEvent) { | |
| var touch = touchEvent.changedTouches[0]; | |
| var e = new MouseEvent(type, { | |
| bubbles: true, | |
| cancelable: true, | |
| clientX: touch.clientX, | |
| clientY: touch.clientY, | |
| screenX: touch.screenX, | |
| screenY: touch.screenY, | |
| button: 99, | |
| buttons: 1 | |
| }); | |
| //Readonly error in strict mode... | |
| //e.buttons = 1; | |
| return e; | |
| } | |
| //https://www.html5rocks.com/en/mobile/touchandmouse/ | |
| target = target || document; | |
| var trackedTouchID; | |
| //A touch will also trigger a mousedown, so this first one may not be necessary: | |
| target.addEventListener('touchstart', function (te) { | |
| trigger('mousedown', te.target, te, true); | |
| }); | |
| target.addEventListener('touchmove', function (te) { | |
| trigger('mousemove', te.target, te); | |
| }); | |
| target.addEventListener('touchend', function (te) { | |
| trigger('mouseup', te.target, te); | |
| }); | |
| function trigger(eventType, target, touchEvent, newTouch) { | |
| var touches = touchEvent.touches; //touchEvent.targetTouches; | |
| if(touches.length !== 1) { | |
| //Don't interfere with pinch-to-zoom and such... | |
| return; | |
| } | |
| var touch = touches[0]; | |
| if(newTouch) { | |
| trackedTouchID = touch.identifier; | |
| } | |
| if(trackedTouchID !== touch.identifier) { | |
| //We lost the original touch during a pinch-to-zoom or other operation. Don't relay this. | |
| //Alternative: Trigger a mouseup and a mousemove first (and maybe even a mousedown), and then this event.. | |
| return; | |
| } | |
| var me = createMouseEvent(eventType, touchEvent); | |
| touchEvent.preventDefault(); | |
| target.dispatchEvent(me); | |
| //console.log(eventType, target.nodeName, touches.length); | |
| } | |
| } | |
| utils.createElement = function(tag, attributes, parent) { | |
| var tagAndClassOrID = tag.split(/([#\.])/); | |
| if((tagAndClassOrID.length === 3) && tagAndClassOrID[2]) { | |
| tag = tagAndClassOrID[0] || 'div'; | |
| attributes = attributes || {}; | |
| //Add either id or class to the attributes list: | |
| attributes[{'#': 'id', '.': 'class'}[tagAndClassOrID[1]]] = tagAndClassOrID[2]; | |
| } | |
| //var element = document.createElement(tag); | |
| var element = parent | |
| //Needed for SVG elements: | |
| ? document.createElementNS(parent.namespaceURI, tag) | |
| : document.createElement(tag); | |
| if(attributes) { | |
| for(var key in attributes) { | |
| element.setAttribute(key, attributes[key]); | |
| } | |
| } | |
| if(parent) { | |
| parent.appendChild(element); | |
| } | |
| return element; | |
| } | |
| utils.relativeMousePos = function(mouseEvent, element, stayWithin) { | |
| function respectBounds(value, min,max) { | |
| return Math.max(min, Math.min(value, max)); | |
| } | |
| var elmBounds = element.getBoundingClientRect(); | |
| var x = mouseEvent.clientX - elmBounds.left, | |
| y = mouseEvent.clientY - elmBounds.top; | |
| if(stayWithin) { | |
| x = respectBounds(x, 0, elmBounds.width); | |
| y = respectBounds(y, 0, elmBounds.height); | |
| } | |
| return { x: x, y: y }; | |
| } | |
| //http://stackoverflow.com/a/15348311/1869660 | |
| //https://jsfiddle.net/ThinkingStiff/FSaU2/ | |
| utils.htmlEncode = function(text) { | |
| return document.createElement('a') | |
| .appendChild(document.createTextNode(text)) | |
| .parentNode.innerHTML; | |
| }; | |
| utils.htmlDecode = function(html) { | |
| var a = document.createElement('a'); | |
| a.innerHTML = html; | |
| return a.textContent; | |
| }; | |
| //https://css-tricks.com/snippets/javascript/get-url-variables/ | |
| utils.getQueryVariable = function(variable) | |
| { | |
| var vars = window.location.search | |
| .substring(1) | |
| .split("&"); | |
| for (var i=0; i<vars.length; i++) { | |
| var pair = vars[i].split("="); | |
| if(pair[0] === variable) { return pair[1]; } | |
| } | |
| return false; | |
| }; | |
| //A list of 104 colors from Tatarize's comment at | |
| //http://godsnotwheregodsnot.blogspot.no/2012/09/color-distribution-methodology.html | |
| // | |
| // ["#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941", "#006FA6", "#A30059", | |
| // "#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762", "#004D43", "#8FB0FF", "#997D87", | |
| // "#5A0007", "#809693", "#FEFFE6", "#1B4400", "#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80", | |
| // "#61615A", "#BA0900", "#6B7900", "#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100", | |
| // "#DDEFFF", "#000035", "#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F", | |
| // "#372101", "#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09", | |
| // "#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1", "#788D66", | |
| // "#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648", "#0086ED", "#886F4C", | |
| // "#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375", "#A3C8C9", "#FF913F", "#938A81", | |
| // "#575329", "#00FECF", "#B05B6F", "#8CD0FF", "#3B9700", "#04F757", "#C8A1A1", "#1E6E00", | |
| // "#7900D7", "#A77500", "#6367A9", "#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700", | |
| // "#549E79", "#FFF69F", "#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329", | |
| // "#5B4534", "#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C"]; | |
| // | |
| //Converted to 3 hex digit color codes (e.g #BF9) => 3 characters per color: | |
| var/*const*/ ColorPalette = '000FF02EFF3FF4408407AA05FDD74000A6FAB960548AF978500899FFE2405C035F435F38665B106700B9FA9F8CB0AC60DEF00375' + | |
| '49B93011AD034087320FB0BFE97BC04BBABF90210497061B6EBF467B76789786857FC9F89C59BC546408E874333BAB0AA433667A' + | |
| 'CCF949885520FCA578CF3900F5C9926070DA7066A953603720D8F990597FE9212748B2F9AB326922543FED45508AC79AE0357634'; | |
| utils.colorPalette = function(i) { | |
| var charIndex = (i % 104) * 3; | |
| var chars = ColorPalette.substring(charIndex, charIndex+3); | |
| return '#' + chars; | |
| }; | |
| utils.createGif1x1 = function(r, g, b) { | |
| //http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever | |
| //http://www.w3.org/Graphics/GIF/spec-gif89a.txt | |
| //http://tomeko.net/online_tools/file_to_hex.php?lang=en | |
| var gif1x1 = [ | |
| //Header, ("GIF89a") | |
| 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, | |
| //Logical Screen Descriptor | |
| 0x01, 0x00, 0x01, 0x00, 0x80, 0x01, 0x00, | |
| //Global color table (r1, g1, b1, r2, g2, b2), second color is "background color" | |
| r, g, b, 0x00, 0x00, 0x00, | |
| //Image descriptor | |
| 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, | |
| //Image data | |
| 0x02, 0x02, 0x44, 0x01, 0x00, | |
| //Trailer (";") | |
| 0x3B | |
| ]; | |
| //http://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string | |
| var b64 = btoa(String.fromCharCode.apply(null, gif1x1)); | |
| return 'data:image/gif;base64,' + b64; | |
| }; | |
| //AJAX - http GET | |
| //http://stackoverflow.com/questions/247483/http-get-request-in-javascript | |
| utils.GET = function(url, onSuccess, onError) { | |
| var request = new XMLHttpRequest(); | |
| request.onreadystatechange = function() { | |
| if (request.readyState === 4) { | |
| if (request.status === 200) { | |
| onSuccess(request.responseText); | |
| } | |
| else if(onError) { | |
| onError(request); | |
| } | |
| } | |
| } | |
| request.open("GET", url, true); | |
| request.send( null ); | |
| }; | |
| utils.printTime = function() { | |
| var d = new Date(); | |
| //return d.toLocaleTimeString() + ' - '; | |
| var millis = ('00' + d.getMilliseconds()).substr(-3); | |
| return d.toTimeString().split(' ')[0] + '.' + millis; | |
| }; | |
| utils.alertErrors = function() { | |
| window.onerror = function(msg, url, linenumber) { | |
| alert(utils.printTime() + ' - Error message: '+msg+'\nURL: '+url+'\nLine Number: '+linenumber); | |
| //return true; | |
| } | |
| }; | |
| utils.log2screen = function() { | |
| var log = utils.createElement('div#abo-log', { | |
| style: 'position:fixed;top:0;left:0;z-index:9999;white-space:pre;pointer-events:none;' | |
| }, document.body); | |
| console._log = console.log; | |
| console.log = function() { | |
| var msg = Array.from(arguments).join(' '); | |
| log.textContent += msg + '\n'; | |
| } | |
| } | |
| })(ABOUtils); | |
| /* Global functions */ | |
| //http://codepen.io/michaelschofield/post/a-useful-function-for-making-queryselectorall-more-like-jquery | |
| function $$(selector, context) { | |
| context = context || document; | |
| var elements = context.querySelectorAll(selector); | |
| return Array.from(elements); | |
| } | |
| function $$1(selector, context) { | |
| context = context || document; | |
| var element = context.querySelector(selector); | |
| return element; | |
| } | |
| /* Polyfills */ | |
| //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc | |
| //http://stackoverflow.com/a/17551105/1869660 | |
| Math.trunc = Math.trunc || function(x) { | |
| return (x < 0) ? Math.ceil(x) : Math.floor(x); | |
| }; | |
| //http://stackoverflow.com/questions/7308627/javascript-calculate-the-nth-root-of-a-number | |
| //http://cwestblog.com/2011/05/06/cube-root-an-beyond/ | |
| Math.nthRoot = Math.nthRoot || function(x, n, log) { | |
| function printError(msg) { | |
| if(log) { console.log('nthRoot error (' + n+'√'+x + '): ' + msg); } | |
| } | |
| var normX = x, root, testX; | |
| //https://en.wikipedia.org/wiki/Nth_root | |
| //"(...) the nth root of a number x, where n is a **positive integer** (...)" | |
| if(n <= 0) { | |
| printError('n must be a positive number'); | |
| return; | |
| } | |
| if(x < 0) { | |
| //https://en.wikipedia.org/wiki/Nth_root | |
| //"if n is even and x is real and negative, none of the nth roots is real." | |
| if(n%2 === 0) { | |
| printError('No *real number* solution'); | |
| return; | |
| } | |
| //Math.pow() doesn't handle negative values for x. | |
| //Make the input positive, and then we'll negate the result (see root below): | |
| normX = -x; | |
| } | |
| root = Math.pow(normX, 1/n); | |
| if(x < 0) { root = -root; } | |
| /* | |
| testX = Math.pow(possibleRoot, n); | |
| if (Math.abs(x - testX) > normX/1000) { | |
| printError("Calculation wasn't accurate enough. Control was " + testX); | |
| return; | |
| } | |
| */ | |
| return root; | |
| } | |
| /* | |
| function testNthRoot(startX, decrementor) { | |
| var x, n, xs, root, | |
| controlX, diff, maxDiff = 0; | |
| startX = startX || 10000; | |
| decrementor = decrementor || 0.9; | |
| for(x = startX; x>Number.EPSILON; x*=decrementor) { | |
| //Test both positive and negative values for x, including integers: | |
| xs = [x]; | |
| if(x>2) { xs.push(Math.trunc(x)); } | |
| xs = xs.concat(xs.map( xx => -xx)); | |
| //console.log(xs); | |
| xs.forEach(function(x) { | |
| for(n = 1; n < 10; n++) { | |
| if((x<0) && (n%2 === 0)) { continue; } | |
| var root = Math.nthRoot(x,n); | |
| if(root === undefined) { | |
| console.log(n+'√'+x+' FAILED'); | |
| } | |
| else { | |
| controlX = Math.pow(root, n); | |
| diff = Math.abs( (x-controlX) / x ); | |
| maxDiff = Math.max(diff, maxDiff); | |
| //console.log(' ' + diff.toFixed(20) + '\t\t(' + n+'√'+x); | |
| } | |
| } | |
| }); | |
| } | |
| console.log('Max diff: ', maxDiff.toFixed(20)); | |
| } | |
| */ | |
| //Trigonometry | |
| //http://codesel.blogspot.no/2014/09/javascript-mathcsc-mathsec-and-mathcot.html | |
| Math.csc = Math.csc || function(x) { return 1 / Math.sin(x); } | |
| Math.sec = Math.sec || function(x) { return 1 / Math.cos(x); } | |
| Math.cot = Math.cot || function(x) { return 1 / Math.tan(x); } | |
| //Optimized: | |
| //http://phrogz.net/angle-between-three-points | |
| Math.findAngle = function(p0, p1, p2) { | |
| // Center point is p1; angle returned in radians | |
| var a = Math.pow(p1.x-p0.x, 2) + Math.pow(p1.y-p0.y, 2), | |
| b = Math.pow(p1.x-p2.x, 2) + Math.pow(p1.y-p2.y, 2), | |
| c = Math.pow(p2.x-p0.x, 2) + Math.pow(p2.y-p0.y, 2); | |
| var angle = Math.acos( (a+b-c) / Math.sqrt(4*a*b) ); | |
| //console.log('findAngle:', p0, '->', p1, '->', p2, ':', angle); | |
| return angle; | |
| } | |
| /* | |
| //http://stackoverflow.com/questions/17763392/how-to-calculate-in-javascript-angle-between-3-points | |
| Math.findAngle = function(A,B,C) { | |
| var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2)); | |
| var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2)); | |
| var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2)); | |
| var angle = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB)); | |
| console.log('findAngle:', A, '->', B, '->', C, ':', angle); | |
| return angle; | |
| } | |
| */ | |
| //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays | |
| Array.from = Array.from || function(list) { | |
| return Array.prototype.slice.call(list); | |
| }; | |
| //Randomize array element order in-place. | |
| //Using Durstenfeld shuffle algorithm. | |
| //http://stackoverflow.com/a/12646864/1869660 | |
| Array.prototype.shuffle = Array.prototype.shuffle || function() { | |
| var array = this; | |
| for (var i = array.length - 1; i > 0; i--) { | |
| var j = Math.floor(Math.random() * (i + 1)); | |
| if(i !== j) { | |
| var temp = array[i]; | |
| array[i] = array[j]; | |
| array[j] = temp; | |
| } | |
| } | |
| return array; | |
| } | |
| /* | |
| * https://github.com/search?q=user%3Ajkroso+svg | |
| * | |
| * The MIT License | |
| * Copyright (c) 2013 Jake Rosoman <[email protected]> | |
| */ | |
| var RosomanSVG = RosomanSVG || {}; | |
| (function(rosvg, undefined) { | |
| // | |
| // https://github.com/jkroso/parse-svg-path/ | |
| // | |
| var length = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0}; | |
| var segment = /([astvzqmhlc])([^astvzqmhlc]*)/ig; | |
| function parse(path) { | |
| function parseValues(args) { | |
| //https://github.com/jkroso/parse-svg-path/issues/1: | |
| args = args.replace(/(\.\d+)(?=\.)/g, '$1 '); | |
| args = args.match(/-?[.0-9]+(?:e[-+]?\d+)?/ig); | |
| return args ? args.map(Number) : []; | |
| } | |
| var data = []; | |
| path.replace(segment, function(_, command, args) { | |
| var type = command.toLowerCase(); | |
| args = parseValues(args); | |
| // overloaded moveTo | |
| if (type == 'm' && args.length > 2) { | |
| data.push([command].concat(args.splice(0, 2))); | |
| type = 'l'; | |
| command = command == 'm' ? 'l' : 'L'; | |
| } | |
| while (true) { | |
| if (args.length == length[type]) { | |
| args.unshift(command); | |
| return data.push(args); | |
| } | |
| if (args.length < length[type]) throw new Error('malformed path data'); | |
| data.push([command].concat(args.splice(0, length[type]))) | |
| } | |
| }); | |
| return data; | |
| } | |
| // | |
| // https://github.com/jkroso/abs-svg-path/ | |
| // | |
| function absolutize(path) { | |
| var startX = 0; | |
| var startY = 0; | |
| var x = 0; | |
| var y = 0; | |
| return path.map(function(seg) { | |
| seg = seg.slice(); | |
| var type = seg[0]; | |
| var command = type.toUpperCase(); | |
| seg.startPoint = { x: x, y: y }; | |
| // is relative | |
| if (type != command) { | |
| seg[0] = command; | |
| switch (type) { | |
| case 'a': | |
| seg[6] += x; | |
| seg[7] += y; | |
| break; | |
| case 'v': | |
| seg[1] += y; | |
| break; | |
| case 'h': | |
| seg[1] += x; | |
| break; | |
| default: | |
| for (var i = 1; i < seg.length; ) { | |
| seg[i++] += x; | |
| seg[i++] += y; | |
| } | |
| break; | |
| } | |
| } | |
| // update cursor state | |
| switch (command) { | |
| case 'Z': | |
| x = startX; | |
| y = startY; | |
| break; | |
| case 'H': | |
| x = seg[1]; | |
| break; | |
| case 'V': | |
| y = seg[1]; | |
| break; | |
| case 'M': | |
| x = startX = seg[1]; | |
| y = startY = seg[2]; | |
| break; | |
| default: | |
| x = seg[seg.length - 2]; | |
| y = seg[seg.length - 1]; | |
| break; | |
| } | |
| seg.endPoint = { x: x, y: y }; | |
| return seg; | |
| }); | |
| } | |
| // | |
| // https://github.com/jkroso/serialize-svg-path/ | |
| // | |
| function serialize(path){ | |
| return path.reduce(function(str, seg) { | |
| return str + seg[0] + seg.slice(1).join(',') | |
| }, ''); | |
| } | |
| rosvg.parse = parse; | |
| rosvg.absolutize = absolutize; | |
| rosvg.serialize = serialize; | |
| })(RosomanSVG); |
| /* */ | |
| /* Fancy checkbox */ | |
| /* */ | |
| .check-switch { | |
| $size: 1.2em; | |
| $width-factor: 2; | |
| $padding: -$size*.1; | |
| $margin: .2em; | |
| @mixin groove-ui() { | |
| display: inline-block; | |
| width: $size*$width-factor; | |
| height: $size; | |
| border-radius: $size/2; | |
| background: whitesmoke; | |
| box-shadow: inset $size*0.05 $size*0.1 $size*0.1 0 rgba(0,0,0,.5); | |
| } | |
| @mixin knob-ui() { | |
| position: absolute; | |
| top:0; left:0; | |
| $knob-size: $size - (2*$padding); | |
| width: $knob-size; | |
| height: $knob-size; | |
| border-radius: $knob-size/2; | |
| margin: $padding; | |
| background: rgba(255,255,255, 0.5); | |
| box-shadow: 0px 1px 4px 0px rgba(0,0,0,.75); | |
| transition: all 0.2s ease-out; | |
| } | |
| @mixin knob-ui-checked() { | |
| left: $size * ($width-factor - 1); | |
| background: rgba(0,255,0, 0.5); | |
| } | |
| /* | |
| Usage: | |
| <label class="check-switch" > | |
| <input type="checkbox" /> | |
| <span class="knob"></span> | |
| </label> | |
| Optional clickable captions can be added inside the label like normal. | |
| Note: The initial idea was to have the UI in the label, and then | |
| hide the <input> somewhere else, for example to implement this trick: | |
| https://developer.mozilla.org/en-US/docs/Web/CSS/:checked#Using_hidden_checkboxes_in_order_to_store_some_CSS_boolean_values | |
| But in CSS there's really no way to find a specific <label> from its corresponding <input> | |
| (so we can toggle the "checked" UI on the correct element) if there is more than one checkbox on the page | |
| (http://stackoverflow.com/questions/1431726/css-selector-for-a-checked-radio-buttons-label | |
| http://stackoverflow.com/questions/16526633/css-selector-of-label-for-type). | |
| Therefore we say that the label must wrap the input; just to know where it is. | |
| */ | |
| //http://stackoverflow.com/questions/17268051/sass-combining-parent-using-ampersand-with-base-element | |
| // label.check-switch { | |
| @at-root label#{&} { | |
| cursor: pointer; | |
| > input { display: none; } | |
| .knob { | |
| @include groove-ui(); | |
| position: relative; | |
| vertical-align: middle; | |
| $actual-margin: if($padding < 0, $margin - $padding, $margin); | |
| margin: $actual-margin; | |
| /*Looks better with surrounding text:*/ | |
| margin-top: $actual-margin/2; | |
| &::after { | |
| content: ''; | |
| @include knob-ui(); | |
| } | |
| } | |
| input:checked ~ .knob:after { | |
| @include knob-ui-checked(); | |
| } | |
| } | |
| /* Single input element. Only works in Chrome.. | |
| @at-root input#{&} { | |
| position: relative; | |
| display: inline-block; | |
| width: 0; height: 0; | |
| font-size: 1em; | |
| vertical-align: super; | |
| //We hid the original checkbox with width/height:0, | |
| //so we make room for the new UI with a margin: | |
| $width: $size*$width-factor; | |
| $knob-overhang: if($padding < 0, -$padding, 0); | |
| $actual-margin: $margin + $knob-overhang; | |
| margin: $actual-margin + $size/2 $actual-margin + $width $actual-margin + $size/2 $actual-margin; | |
| &::before, &::after { | |
| content: ''; | |
| position: absolute; | |
| } | |
| //Groove | |
| &::before { | |
| @include groove-ui(); | |
| top: -$size/2; left:0; | |
| } | |
| //Knob | |
| &::after { | |
| @include knob-ui(); | |
| top: -$size/2; | |
| } | |
| &:checked::after { | |
| @include knob-ui-checked(); | |
| } | |
| } | |
| */ | |
| } |