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(); | |
} | |
} | |
*/ | |
} |