Skip to content

Instantly share code, notes, and snippets.

@Jeremboo
Last active January 14, 2024 02:53
Show Gist options
  • Save Jeremboo/cb55bdbff02c96f89e23 to your computer and use it in GitHub Desktop.
Save Jeremboo/cb55bdbff02c96f89e23 to your computer and use it in GitHub Desktop.
Useful functions on javascript es2015 : Geometry2D/3D, Math, Canvas, DOM, Color, Node...
/*******
* MATH
*******/
// http://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
export const randInt(min, max) {
min = Math.ceil(min);
return Math.floor(Math.random() * (Math.floor(max) - min + 1)) + min;
}
export const randFloat = (min, max) => (Math.random() * (max - min) + min);
// Combine min() and max()
// https://stackoverflow.com/questions/11409895/whats-the-most-elegant-way-to-cap-a-number-to-a-segment
export const clamp = (min, max, x) => Math.min(Math.max(x, min), max);
export const lerp = (min, max, x) => max + (x * (min - max)); // (0, 10, 0.5) = 5;
export const inverseLerp = (min, max, x) => (x - min) / (max - min); // (0, 10, 5) = 0.5;
export const sqr = a => a * a;
// http://cwestblog.com/2012/11/12/javascript-degree-and-radian-conversion/
export const radians = degrees => degrees * Math.PI / 180;
export const degrees = radians => radians * 180 / Math.PI;
/*******
* GEOMETRY 2D
*******/
export const getAngleBetweenTwoVec2 = (x1, y1, x2, y2) => Math.atan2(y1 - y2, x1 - x2); // RADIAN
export const getDistBetweenTwoVec2 = (x1, y1, x2, y2) => Math.sqrt(Math.sqr(y2 - y1) + Math.sqr(x2 - x1));
// More complete informations returned
export const getDistBetweenTwoVec2 = (x1, y1, x2, y2) => {
const x = x1 - x2
const y = y1 - y2
return { x, y, force: Math.sqrt(sqr(y) + sqr(x)) }
}
/*******
* GEOMETRY 3D / THREE.JS
*******/
import { Matrix4, Euler, Vector3 } from 'three';
export const getRotationMatrix = vectRotation => {
const m = new Matrix4();
const m1 = new Matrix4();
const m2 = new Matrix4();
const m3 = new Matrix4();
m1.makeRotationX(-vectRotation.x);
m2.makeRotationY(-vectRotation.y);
m3.makeRotationY(-vectRotation.z);
m.multiplyMatrices(m1, m2);
m.multiply(m3);
return m;
};
export const getRandomEuler = () => new Euler(
getRandomFloat(0, 6.2831),
getRandomFloat(0, 6.2831),
getRandomFloat(0, 6.2831),
);
export const getRandomNormalizedVector3 = () => new Vector3(
getRandomFloat(-1, 1),
getRandomFloat(-1, 1),
getRandomFloat(-1, 1),
);
export const getRandomPosWithinASphere = (r) => {
const theta = getRandomFloat(0, Math.PI * 2);
const phi = getRandomFloat(-Math.PI * 0.5, Math.PI * 0.5);
return new Vector3(
r * Math.cos(theta) * Math.cos(phi),
(r * 0.9) * Math.sin(phi),
r * Math.sin(theta) * Math.cos(phi),
);
};
export const getNormalizedPosFromScreen = (x, y) => new Vector3(
((x / window.innerWidth) * 2) - 1,
-((y / window.innerHeight) * 2) + 1,
0.5,
);
// https://stackoverflow.com/questions/13055214/mouse-canvas-x-y-to-three-js-world-x-y-z
export const getDistanceBetweenNormalizedMousePosAndPos = (normalizedMousePos, pos, camera) => {
const nm = normalizedMousePos.clone()
nm.unproject(camera);
const dir = nm.sub(camera.position).normalize();
const distance = (pos.z - camera.position.z) / dir.z;
const mousePos = camera.position.clone().add(dir.multiplyScalar(distance));
return pos.clone().sub(mousePos);
};
// Ty Robin <3
// https://codepen.io/robin-dela/pen/dZXVrQ?editors=0010
// https://threejs.org/docs/#api/core/Raycaster
export const addCursorMoveListener = (mesh, camera, scene, callback) => {
const raycaster = new Raycaster();
const moveEvent = 'ontouchstart' in (window || navigator.msMaxTouchPoints) ? 'touchmove' : 'mousemove';
window.addEventListener(moveEvent, (e) => {
const mouseVec = getNormalizedPosFromScreen(
e.clientX || e.touches[0].clientX,
e.clientY || e.touches[0].clientY,
);
raycaster.setFromCamera(mouseVec, camera);
const intersects = raycaster.intersectObjects(scene.children);
let i = 0;
let targetedObjectIntersected = false;
while (i < intersects.length && !targetedObjectIntersected) {
if (intersects[i].object.uuid === mesh.uuid) {
targetedObjectIntersected = true;
callback(intersects[i]);
}
i += 1;
}
});
};
export const worldToLocalDirection = ( object, worldDirectionVector, localDirectionVector ) => {
object.updateMatrixWorld()
localDirectionVector.copy( worldDirectionVector ).applyQuaternion( object.getWorldQuaternion().inverse() )
return localDirectionVector
}
// https://medium.com/@Zadvorsky/into-vertex-shaders-part-2-emulating-the-3d-graphics-pipeline-41e06a8b49a4
export const getWorldMatrix = (mesh) => new Matrix4().compose(
child.position,
new Quaterion().setFromEuler(mesh.rotation),
child.scale
);
export const getModelViewMatrix = (mesh) => new Matrix4().multiplyMatrices(
camera.matrixWorldInverse,
getWorldMatrix(mesh)
);
/**
* Get the width and the height of the field size from a specific distance
*/
export const getCameraVisionFieldSizeFromPosition = (position = new Vector3(), camera) => {
// (2 * Math.tan(radians(camera.fov) / 2)) => Always the same number. Can be optimized
const height = (2 * Math.tan(radians(camera.fov) / 2)) * (camera.position.distanceTo(position));
const width = camera.aspect * height;
return {
width,
height,
};
};
/*******
* MOBILE ORIENTATION
*******/
// http://stackoverflow.com/questions/35283320/three-js-rotate-camera-with-both-touch-and-device-orientation
// https://threejs.org/examples/misc_controls_deviceorientation.html
import { Vector3, Euler, Quaternion } from 'three';
export const updateRotationFromMobileOrientation = (object, { alpha = 0, beta = 0, gamma = 0, orient = 0, alphaOffsetAngle = 0 }) => {
const zee = new Vector3(0, 0, 1);
const euler = new Euler();
const q0 = new Quaternion();
const q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)); // - PI/2 around the x-axis
euler.set(radians(beta), radians(alpha) + alphaOffsetAngle, -radians(gamma), 'YXZ'); // 'ZXY' for the device, but 'YXZ' for us
object.quaternion.setFromEuler(euler); // orient the device
object.quaternion.multiply(q1); // camera looks out the back of the device, not the top
// object.quaternion.multiply(q0.setFromAxisAngle(zee, radians(orient))); // adjust for screen orientation
};
// onMovileOrientationEvent((data) => {
// updateRotationFromMobileOrientation(threejsObject, data);
// });
export const onMobileOrientationEvent = (callback) => {
const updateOrientation = event => {
const { alpha, beta, gamma } = event;
callback({ alpha, beta, gamma, orient: window.orientation || 0 });
};
window.addEventListener('deviceorientation', updateOrientation);
window.addEventListener('orientationChange', updateOrientation);
};
/*******
* HTML
*******/
// http://stackoverflow.com/questions/14600129/remove-html-and-javascript-comments-automatically
export const stripHtmlComments = (content) => content.replace(/<!--(?!>)[\S\s]*?-->/g, '');
// http://stackoverflow.com/questions/211703/is-it-possible-to-get-the-position-of-div-within-the-browser-viewport-not-withi
export const getPositionInViewport = (e) => {
const offset = {
x: e.offsetWidth / 2,
y: e.offsetHeight / 2,
};
while (e) {
offset.x += e.offsetLeft;
offset.y += e.offsetTop;
e = e.offsetParent;
}
if (document.documentElement && (document.documentElement.scrollTop || document.documentElement.scrollLeft)) {
offset.x -= document.documentElement.scrollLeft;
offset.y -= document.documentElement.scrollTop;
} else if (document.body && (document.body.scrollTop || document.body.scrollLeft)) {
offset.x -= document.body.scrollLeft;
offset.y -= document.body.scrollTop;
} else if (window.pageXOffset || window.pageYOffset) {
offset.x -= window.pageXOffset;
offset.y -= window.pageYOffset;
}
return offset;
};
// https://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling/22480938#22480938
export const isScrolledIntoView = (el) => {
const { top, bottom } = el.getBoundingClientRect();
return (bottom > 0) && (top < window.innerHeight);
};
// http://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript
export const removeAllNodeChild = (parentElm) => {
while (parentElm.firstChild) {
parentElm.removeChild(parentElm.firstChild);
}
};
export const offsetTop = (element) => {
if (!element) {
return 0;
}
return element.offsetTop + offsetTop(element.offsetParent);
};
// https://stackoverflow.com/questions/8158012/how-to-scale-an-image-to-full-screen-using-javascript/8160528#8160528
export const fullScreenPreserveRatio = (elm) => {
const elmW = parseInt(elm.style.width, 10);
const elmH = parseInt(elm.style.height, 10);
const maxWidth = window.innerWidth;
const maxHeight = window.innerHeight;
const widthRatio = maxWidth / elmW;
const heightRatio = maxHeight / elmH;
let ratio = widthRatio;
if (widthRatio * elmH > maxHeight) {
ratio = heightRatio;
}
elm.style.width = `${elmW * ratio}px`;
elm.style.height = `${elmH * ratio}px`;
};
/*******
* MOBILE
*******/
// http://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
export const mobileCheck = () => {
let check = false;
((a) => {if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
return check;
};
/*******
* AJAX / LOADING
*******/
export const loadImage = (url) => new Promise((resolve, reject) => {
const downloadingImage = new Image();
downloadingImage.onload = () => {
resolve(downloadingImage);
};
downloadingImage.onerror = () => {
reject();
};
downloadingImage.src = url;
});
// http://stackoverflow.com/questions/6150289/how-to-convert-image-into-base64-string-using-javascript
export const toDataUrl = (url, callback) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = () => {
const reader = new FileReader();
reader.onloadend = () => {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.send();
};
// http://stackoverflow.com/questions/14388452/how-do-i-load-a-json-object-from-a-file-with-ajax
export const fetchJSONFile = (path, callback) => {
const httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = () => {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
const data = JSON.parse(httpRequest.responseText);
if (callback) callback(data);
}
}
};
httpRequest.open('GET', path);
httpRequest.send();
};
// LoadVideo
const md = new MobileDetect(window.navigator.userAgent);
const onIOS = md.is('iPhone'); // FIX use native detection
export const addVideo = (videoElement, url) => new Promise((resolve, reject) => {
if (onIOS) videoElement.autoplay = true;
videoElement.addEventListener('loadeddata', () => {
loaded = true;
loader.style.display = 'none';
videoElement.pause();
resolve();
});
videoElement.addEventListener('error', reject);
videoElement.src = url;
});
/*******
* ANIMATION
*******/
export const easing = (target, value, { vel = 0.03, update = f => f, callback = f => f } = {}) => {
const f = (target - value) * vel;
update(f);
if (Math.abs(f) < 0.001) {
callback();
}
};
export const easingRAF = (target, value, { vel = 0.03, update = f => f, callback = f => f } = {}) => {
const f = (target - value) * vel;
const newValue = value + f;
update(newValue);
if (Math.abs(f) < 0.001) {
update(target);
callback();
return;
}
requestAnimationFrame(easing.bind(this, target, newValue, { vel, update, callback }));
};
/*******
* URL
*******/
// http://stackoverflow.com/questions/979975/how-to-get-the-value-from-the-get-parameters
export const queryString = () => {
// This function is anonymous, is executed immediately and
// the return value is assigned to QueryString!
const queryString = {};
const query = window.location.search.substring(1);
const vars = query.split('&');
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=');
// If first entry with this name
if (typeof queryString[pair[0]] === 'undefined') {
queryString[pair[0]] = decodeURIComponent(pair[1]);
// If second entry with this name
} else if (typeof queryString[pair[0]] === 'string') {
const arr = [queryString[pair[0]], decodeURIComponent(pair[1])];
queryString[pair[0]] = arr;
// If third or later entry with this name
} else {
queryString[pair[0]].push(decodeURIComponent(pair[1]));
}
}
return queryString;
};
// http://stackoverflow.com/questions/824349/modify-the-url-without-reloading-the-page/3354511#3354511
export const updateQueryStringUrlParameter = (key, value) => {
const uri = window.location.href;
const re = new RegExp(`([?&])${key}=.*?(&|$)`, 'i');
const separator = uri.indexOf('?') !== -1 ? '&' : '?';
const newUrl = uri.match(re)
? uri.replace(re, `$1${key}=${value}$2`)
: `${uri}${separator}${key}=${value}`
;
window.history.pushState({}, '', newUrl);
};
/*******
* [R, G, B]
*******/
export const getRGBFromImageData = (imageData, lineLenght, x, y) => {
const pixelPosition = (y * (lineLenght * 4)) + (x * 4);
return [
imageData[pixelPosition],
imageData[pixelPosition + 1],
imageData[pixelPosition + 2],
];
};
/*******
* HEXA && CSS RGB
*******/
export const getRandomHexa = () =>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'][Math.floor(Math.random() * 16)]
;
export const getRandomColor = (type = '#') => {
const color = `${type}`;
for (let i = 0; i < 6; i++ ) {
color += getRandomHexa();
}
return color;
}
export const getCSSRGBColor = RGB =>
(`rgb(${Math.round(RGB[0])}, ${Math.round(RGB[1])}, ${Math.round(RGB[2])})`)
;
// http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
export const componentToHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
export const RBGToHex = (RGB, prepend = '#') =>
prepend + componentToHex(RGB[0]) + componentToHex(RGB[1]) + componentToHex(RGB[2])
;
/*******
* OHTER COLOR TYPE
*******/
import { Vector4 } from 'three';
export const getVec4Color = color => new Vector4(
color[0] / 256,
color[0] / 256,
color[0] / 256,
1
);
/*******
* CANVAS
*******/
export const canvasBuilder = (width = window.innerWidth, height = window.innerHeight) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
return {
canvas,
context,
getImageData: () => context.getImageData(0, 0, width, height).data,
};
};
/*******
* NOISE
*******/
// https://gist.github.com/donpark/1796361
export const randomNoise = (width = window.innerWidth, height = window.innerHeight) => {
const { canvas, context, getImageData } = canvasBuilder(width, height);
const pixels = getImageData();
const length = pixels.length;
let i = 0;
while (i < length) {
pixels[i++] = pixels[i++] = pixels[i++] = (Math.random() * 256) | 0;
pixels[i++] = 255;
}
context.putImageData(imageData, 0, 0);
return canvas;
};
export const perlinNoise = (width = window.innerWidth, height = window.innerHeight) => {
const { canvas, context } = canvasBuilder(width, height);
const noise = randomNoise(width, height);
context.save();
/* Scale random iterations onto the canvas to generate Perlin noise. */
let size;
for (size = 4; size <= width; size *= 2) {
const x = (Math.random() * (width - size)) | 0;
const y = (Math.random() * (height - size)) | 0;
context.globalAlpha = 10 / size;
context.drawImage(noise, x, y, size, size, 0, 0, width, height);
}
context.restore();
return canvas;
};
export const gradientPerlinNoise = (width = window.innerWidth, height = window.innerHeight ) => {
const { canvas, context } = canvasBuilder(width, height);
const pNoise = perlinNoise(width, height);
const gradient = context.createLinearGradient(0, 0, width, 0);
gradient.addColorStop(0.1, 'rgba(0, 0, 0, 0.8)');
gradient.addColorStop(0.9, 'rgba(255, 255, 255, 0.6)');
context.drawImage(pNoise, 0, 0);
context.fillStyle = gradient;
context.fillRect(0, 0, width, height);
return canvas;
};
/*******
* CANVAS OR IMAGE
*******/
window.URL = window.URL || window.webkitURL;
export const applyImageToCanvas = (url, w, h, handling) => new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = (e) => {
if (e.target.status === 200) {
const blob = e.target.response;
const image = new Image();
image.crossOrigin = 'Anonymous';
image.onload = () => {
const width = w || image.width;
const height = h || image.height;
const canvasB = canvasBuilder(width, height);
const { canvas, context } = canvasB;
context.globalAlpha = 1;
switch(handling) {
case 'mosaique':
const nbrTileColumn = width / image.width;
const nbrTileLine = height / image.height;
let i;
let j;
for (i = 0; i < nbrTileLine; i++) {
for (j = 0; j < nbrTileColumn; j++) {
context.drawImage(image, j * image.width, i * image.height);
}
}
break;
case 'cover':
break;
default:
context.drawImage(image, 0, 0, width, height);
break;
}
window.URL.revokeObjectURL(blob);
// resolve(Object.assign(canvasB, { image }));
resolve(canvas);
};
image.onerror = () => {
reject('Err : Canvas cannot be loaded');
};
image.src = window.URL.createObjectURL(blob);
}
};
xhr.send();
});
// https://developer.mozilla.org/fr/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
export const globalCompositeOpetationSupported = operation => {
const { context } = canvasBuilder();
context.globalCompositeOperation = operation;
return (context.globalCompositeOperation === operation);
};
export const applyGlobalCompositeOperation = (existingCanvasImg, newCanvasImg, operation) => {
const { canvas, context } = canvasBuilder(existingCanvasImg.width, existingCanvasImg.height);
context.drawImage(existingCanvasImg, 0, 0);
context.globalCompositeOperation = operation;
if (context.globalCompositeOperation !== operation) {
// When the operation it doesn't supported
// https://github.com/mozilla/pdf.js/issues/3900
if (operation === 'multiply') {
context.globalAlpha = 0.5;
}
}
context.drawImage(newCanvasImg, 0, 0);
return canvas;
};
export const tintCanvasImg = (canvasImg, color) => {
const w = canvasImg.width;
const h = canvasImg.height;
const { canvas, context } = canvasBuilder(w, h);
context.fillStyle = color;
context.fillRect(0, 0, w, h);
const tintedCanvas = applyGlobalCompositeOperation(canvas, canvasImg, 'multiply');
return applyGlobalCompositeOperation(tintedCanvas, canvasImg, 'destination-in');
};
export const tintCanvasImgToIE = (canvasImg, color, maskCanvasImg) => {
const w = canvasImg.width;
const h = canvasImg.height;
const mask = maskCanvasImg || canvasImg;
const { canvas, context } = canvasBuilder(w, h);
context.fillStyle = color;
context.fillRect(0, 0, w, h);
const tintedCanvas = canvasBuilder(w, h);
tintedCanvas.context.drawImage(canvas, 0, 0);
tintedCanvas.context.globalAlpha = 0.4;
tintedCanvas.context.drawImage(canvasImg, 0, 0);
tintedCanvas.context.globalAlpha = 1;
return applyGlobalCompositeOperation(tintedCanvas, mask, 'destination-in');
};
export const applyShadowToCanvasImg = (canvasImg, color = '#494949') => {
const blurSize = 10;
const { canvas, context } = canvasBuilder(canvasImg.width + blurSize, canvasImg.height + (blurSize * 5));
context.shadowOffsetX = blurSize * 0.4;
context.shadowOffsetY = blurSize * 0.3;
context.shadowColor = color;
context.shadowBlur = blurSize * 0.8;
context.drawImage(canvasImg, blurSize * 0.5, 0);
return canvas;
};
export const flipCanvasImg = canvasImg => {
const w = canvasImg.width;
const h = canvasImg.height;
const { canvas, context } = canvasBuilder(w, h);
context.save();
context.scale(-1, 1);
context.drawImage(canvasImg, -w, 0, w, h);
context.restore();
return canvas;
};
export const resizeCanvasImg = (canvasImg, w, h) => {
const { canvas, context } = createCanvas(w, h);
context.drawImage(canvasImg, 0, 0, w, h);
return canvas;
};
/*******
* SHAPE
*******/
export const gradientArc = (ctx, { x = 0, y = 0, size = 10, ratio = 0.5 } = {}) => {
const canvasB = canvasBuilder(ctx.canvas.width, ctx.canvas.height);
// create with the temps canvas
const gradStyle = canvasB.context.createRadialGradient(x, y, 1, x, y, size);
gradStyle.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradStyle.addColorStop(ratio, 'rgba(0, 0, 0, 0.5)');
gradStyle.addColorStop(1, 'rgba(0, 0, 0, 0)');
canvasB.context.fillStyle = gradStyle;
canvasB.context.arc(x, y, size, 0, Math.PI * 2);
canvasB.context.fill();
ctx.drawImage(canvasB.canvas, 0, 0);
};
/*******
* TESTS
*******/
export const testPerf = (fct, ...params) => {
const t0 = performance.now();
const result = fct(...params);
const t1 = performance.now();
console.log(`PERF === ${t1 - t0} ms.`);
return result;
};
export const getAndTestExtention = (filePath, extentionToTest = ['.mp4', '.gif', '.png', '.jpg']) => {
if (!existsSync(filePath)) {
return false;
}
return (extentionToTest.indexOf(extname(filePath)) !== -1);
};
export const isJSON = (str) => {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
};
/*******
* ARRAY
*******/
export const traverseArr = (arr, fct) => {
let i = arr.length;
for (i; i > 0; i--) {
fct(arr[i]);
}
};
// http://stackoverflow.com/questions/5767325/how-to-remove-a-particular-element-from-an-array-in-javascript
export const removeInstanceFromArray = (arr, instance) => {
const index = arr.indexOf(instance);
if (index !== -1) arr.splice(index, 1);
return arr;
};
/*******
* DATE
*******/
export const timestampToClockFormat = (tmp) => {
const date = new Date(tmp);
const addZero = (v) => v < 10 ? `0${v}` : v;
return `${addZero(date.getHours() - ((tmp < 3600000) ? 1 : 0))
}:${addZero(date.getMinutes())
}:${addZero(date.getSeconds())
}`;
};
export const getFrenchDateAndHour = (d) =>
`${d.toLocaleDateString('fr-FR')} à ${d.toTimeString().slice(0, -15)}`
;
/*******
* HELPERS
*******/
export const logError = (message) => {
console.warn(`### ERROR ->
${message}
`);
};
export const logEvent = (message) => {
console.log(`${getFrenchDateAndHour()} ->
${message}
`);
};
export const logInfo = (message) => {
console.log(` - ${message}`);
};
/*******
* OTHER
*******/
export const componentToHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
/**
* DIRECTORY
* require('fs');
* require('camelcase');
*/
/**
* test if a path exist
* @param {String} path the path to test
* @return {Boolean} answer
*/
const pathExist = path => {
if (!fs.existsSync(path)) {
console.log(`ERROR : ${path} does not exist !`);
return false;
}
return true;
};
/**
* Create a directory into the path passed into parameter
* @param {String} parentPath valid parent path
* @param {String} [type=''] name of the type of path
* @return {Object} name and pathName
*/
const createDir = (parentPath, type = '') => {
const name = ask(`${type} name : `);
const nameToCamelCase = camelCase(name);
const path = `${parentPath}${nameToCamelCase}`;
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
} else {
console.log(`ERROR : ${path} already exist ! Please write another.`);
return createDir(parentPath, type);
}
return { name, path };
};
/**
* Create a directory into the path passed into parameter
* @param {String} parentPath valid parent path
* @param {Array} arr array of directory name
* @return {String} the final path
*/
const createDirsDepth = (parentPath, arr) => {
let i;
let recurcivePath = `${parentPath}`;
const length = arr.length;
for (i = 0; i < length; i++) {
createDir(recurcivePath, arr[i]);
recurcivePath += `${arr[i]}/`;
}
return recurcivePath;
};
/**
* ASK
* require('readline-sync');
* require('fs');
*/
/**
* ask a simple question
* @param {String} question the question to ask to the user
* @return {String} the answer from user
*/
const ask = question => readlineSync.question(question);
/**
* ask a question to selected an answer into an array.
* @param {Array} arr array of possiblities
* @param {String} type type of values to select
* @return {arr[]} one value of the array
*/
const askWitchChoice = (arr, type = '') =>
arr[readlineSync.keyInSelect(arr, `Witch ${type} ? : `)]
;
/**
* ask a question to select a child directory from a path
* @param {String} dirPath the parent path
* @param {String} type type of values to select
* @return {String} directory path selected
*/
const askWitchChildDir = (dirPath, type) => {
const dir = fs.readdirSync(dirPath);
removeInstanceFromArray(dir, '.DS_Store');
removeInstanceFromArray(dir, 'data.json');
return askWitchChoice(dir, type);
};
/**
* ask a boolean question.
* @param {String} question the question to ask to the user
* @param {Boolean} [defaultValue=true] the default value if the answer is null
* @return {Boolean} answer
*/
const askBool = (question, defaultValue = true) => {
const trueValue = ['y', 'yes', 'yeah', 'yep', 'oui'];
const falseValue = ['n', 'no', 'nah', 'nope'];
if (defaultValue) {
trueValue.push('');
} else {
falseValue.push('');
}
const answer = readlineSync.question(
`${question} (${defaultValue ? 'yes' : 'no'})`, { trueValue, falseValue }
);
if (answer === true || answer === false) {
return answer;
}
console.log('Please, answer with a correct value.')
return askBool(question, defaultValue);
};
/*******
* KEY
*******/
export const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
export const guid = () => `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
/**
* Move the camera AROUND the tageted position.
* @param {Vector3} position targeted position
* @param {Vector3} target targeted position
* @param {Function} onComplete callback after the aniumation
* @return {Tween} Return the tween to add it on another animation
*/
getTmMoveCameraAroundTarget (position, target, onComplete = f => f) {
// Compute initial vector2
const originVec2 = { x: target.x, y: target.z } // TODO could be global
const currentVec2 = { x: this.camera.position.x, y: this.camera.position.z }
const targetedVec2 = { x: position.x, y: position.z }
// Set the current values
const v = {
angle: AngleVec2(currentVec2, originVec2), // current angle
dist: DistVec2(currentVec2, originVec2), // current distance
y: this.camera.position.y // current height
}
// Animate to the targeted values
return TweenLite.to(v, 0.5, {
angle: AngleVec2(targetedVec2, originVec2), // targeted angle
dist: DistVec2(targetedVec2, originVec2), // targeted distance
y: position.y, // targeted height
onComplete,
onUpdate: () => {
// Update the camera position to move around the origin
this.camera.position.set(
originVec2.x + (v.dist * Math.cos(v.angle)),
v.y,
originVec2.y + (v.dist * Math.sin(v.angle))
)
// Always look at the origin
this.camera.lookAt(target)
}
})
}
export const GetSVGElmWithAttribute = (wrapper, svgElmName, attributeName) =>
Array.from(wrapper.querySelectorAll(svgElmName)).filter((path) => {
const attr = path.getAttribute(attributeName)
return attr && attr !== 'none'
})
/*******
* ENCODER / DECODER
*******/
// https://stackoverflow.com/questions/15341912/how-to-go-from-blob-to-arraybuffer
// https://stackoverflow.com/questions/26946548/javascript-file-uploading-readasarraybuffer
export const arrayBufferToBlob = (arrayBuffer) => {
const uint8Array = new Uint8Array(arrayBuffer);
return new Blob([uint8Array.buffer]);
};
export const blobToArrayBuffer = blob => new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = (e) => {
resolve(new Int8Array(e.target.result));
};
// TODO add reject
fileReader.readAsArrayBuffer(blob);
});
export const audioBlobToAudio = audioBlob => new Promise((resolve, reject) => {
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
audio.addEventListener('loadedmetadata', () => {
audio.addEventListener('durationchange', () => {
if (audio.duration !== Infinity) {
resolve(audio);
}
});
});
audio.load();
});
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
export const uint8ArrayToString = buf => String.fromCharCode.apply(null, buf);
export const arrayBufferToString = buf => String.fromCharCode.apply(null, new Uint8Array(buf));
export const stringToArrayBuffer = (str) => {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment