Skip to content

Instantly share code, notes, and snippets.

@Sphinxxxx
Created March 23, 2017 13:18
Show Gist options
  • Save Sphinxxxx/e81bbc89f086e80656a098f079dab682 to your computer and use it in GitHub Desktop.
Save Sphinxxxx/e81bbc89f086e80656a098f079dab682 to your computer and use it in GitHub Desktop.
ABOUtils
<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 <jkroso@gmail.com>
*/
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();
}
}
*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment