UNPKG

spin-wheel

Version:

An easy to use, themeable component for randomising choices and prizes.

220 lines (184 loc) 6.2 kB
/** * Get a random integer between `min` (inclusive) and `max` (exclusive). */ export function getRandomInt(min = 0, max = 0) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min)) + min; } /** * Get a random number between `min` (inclusive) and `max` (inclusive). * Control the number of decimal places with `round`. */ export function getRandomFloat(min = 0, max = 0, round = 14) { return parseFloat((Math.random() * (max - min) + min).toFixed(round)); } /** * Convert degrees to radians. */ export function degRad(degrees = 0) { return degrees * Math.PI / 180; } /** * Return true if `angle` is between `arcStart` (inclusive) and `arcEnd` (exclusive). * Example: `(0, 359, 1) == true` * Example: `(0, 1, 2) == false` */ export function isAngleBetween(angle, arcStart, arcEnd) { if (arcStart < arcEnd) return arcStart <= angle && angle < arcEnd; return arcStart <= angle || angle < arcEnd; } /** * Average the values in `array`. * Only operate on truthy values. * Truthy values that are not Numbers count as 1. * Return 0 if the array is empty. */ export function aveArray(array = []) { let sum = 0; for (const val of array) { if (val) sum += ((typeof val === 'number') ? val : 1); } return sum / array.length || 0; } /** * Calculate the largest font size that `text` can have without exceeding `maxWidth`. * Won't work unless `fontFamily` has been loaded. */ export function getFontSizeToFit(text, fontFamily, maxWidth, canvasContext) { canvasContext.save(); canvasContext.font = `1px ${fontFamily}`; const w = canvasContext.measureText(text).width; canvasContext.restore(); return maxWidth / w; } /** * Return true if the given point is inside a circle. * cx, cy is circle center. * radius is circle radius. */ export function isPointInCircle(point = {x: 0, y: 0}, cx, cy, radius) { const distanceSquared = ((point.x - cx) ** 2) + ((point.y - cy) ** 2); return distanceSquared <= (radius ** 2); } /** * Translate the given point from the viewport's coordinate space to the element's coordinate space. */ export function translateXYToElement(point = {x: 0, y: 0}, element = {}, devicePixelRatio = 1) { const rect = element.getBoundingClientRect(); return { x: (point.x - rect.left) * devicePixelRatio, y: (point.y - rect.top) * devicePixelRatio, }; } export function getMouseButtonsPressed(event = {}) { return [1, 2, 4, 8, 16].filter(i => event.buttons & i); } /** * Source: https://stackoverflow.com/a/47653643/737393 */ export function getAngle(originX, originY, targetX, targetY) { const dx = originX - targetX; const dy = originY - targetY; let theta = Math.atan2(-dy, -dx); theta *= 180 / Math.PI; if (theta < 0) theta += 360; return theta; } /** * Return the distance between two points. */ export function getDistanceBetweenPoints(point1 = {x: 0, y: 0}, point2 = {x: 0, y: 0}) { return Math.hypot(point2.x - point1.x, point2.y - point1.y); } /** * Add two angles together. * Return a positive number between 0 and 359.9999. */ export function addAngle(a = 0, b = 0) { const sum = a + b; let result; if (sum > 0) { result = sum % 360; } else { result = 360 + (sum % 360); } if (result === 360) result = 0; return result; } /** * Return the shortest difference (in degrees) between two angles. * Only accept angles between 0 and 360. */ export function diffAngle(a = 0, b = 0) { const offsetFrom180 = 180 - b; const aWithOffset = addAngle(a, offsetFrom180); return 180 - aWithOffset; } /** * Calculate what the new rotation of a wheel should be, so that the relative angle `targetAngle` will be at 0 degrees (north). * targetAngle = a value between 0 and 360. * direction = the direction the wheel can spin. 1 for clockwise, -1 for antiClockwise. */ export function calcWheelRotationForTargetAngle(currentRotation = 0, targetAngle = 0, direction = 1) { let angle = ((currentRotation % 360) + targetAngle) % 360; // Ignore tiny values. // Due to floating point arithmetic, ocassionally the input angles won't add up exactly // and this can push the angle slightly above 360. angle = fixFloat(angle); // Apply direction: angle = ((direction === 1) ? (360 - angle) : 360 + angle) % 360; angle *= direction; return currentRotation + angle; } export function isObject(v) { return typeof v === 'object' && !Array.isArray(v) && v !== null; } export function isNumber(n) { return typeof n === 'number' && !Number.isNaN(n); } export function setProp({val, isValid, errorMessage, defaultValue, action = null}) { if (isValid) { return (action) ? action() : val; } else if (val === undefined) { return defaultValue; } throw new Error(errorMessage); } export function fixFloat(f = 0) { return Number(f.toFixed(9)); } /** * Easing function. */ export function easeSinOut(n) { return Math.sin((n * Math.PI) / 2); } export function getResizeObserver(element = {}, callBack = {}) { if (window.ResizeObserver) { const observer = new ResizeObserver(() => { // We need to trigger a re-draw because there will be a canvas flicker when there is throttling on drawing. // Probably because the observer is called in the same animation frame as the resize event. // Note: the `Window.resize` event (see below) doesn't cause this flicker for some reason. // See this thread for a description of this issue: https://github.com/pixijs/pixijs/issues/3395#issuecomment-328334633 callBack({redraw: true}); }); observer.observe(element); return { stop: () => { // TODO: test observer.unobserve(element); observer.disconnect(); }, }; } // The browser does not support ResizeObserver. // Fallback to using `Window.resize`. // This method will not work in all scenarios, and you may still need to manually trigger a refresh. // For example when the element is inside a dialog, or when setting css display style to `none`. window.addEventListener('resize', callBack); return { stop: () => { window.removeEventListener('resize', callBack); }, }; }