UNPKG

animejs

Version:

JavaScript animation engine

262 lines (230 loc) 7.78 kB
/** * Anime.js - core - ESM * @version v4.3.6 * @license MIT * @copyright 2026 - Julian Garnier */ import { isBrowser, maxValue, minValue, hexTestRgx, lowerCaseRgx, validRgbHslRgx } from './consts.js'; import { globals } from './globals.js'; /** * @import { * Target, * DOMTarget, * } from '../types/index.js' */ // Strings /** * @param {String} str * @return {String} */ const toLowerCase = str => str.replace(lowerCaseRgx, '$1-$2').toLowerCase(); /** * Prioritize this method instead of regex when possible * @param {String} str * @param {String} sub * @return {Boolean} */ const stringStartsWith = (str, sub) => str.indexOf(sub) === 0; // Note: Date.now is used instead of performance.now since it is precise enough for timings calculations, performs slightly faster and works in Node.js environement. const now = Date.now; // Types checkers const isArr = Array.isArray; /**@param {any} a @return {a is Record<String, any>} */ const isObj = a => a && a.constructor === Object; /**@param {any} a @return {a is Number} */ const isNum = a => typeof a === 'number' && !isNaN(a); /**@param {any} a @return {a is String} */ const isStr = a => typeof a === 'string'; /**@param {any} a @return {a is Function} */ const isFnc = a => typeof a === 'function'; /**@param {any} a @return {a is undefined} */ const isUnd = a => typeof a === 'undefined'; /**@param {any} a @return {a is null | undefined} */ const isNil = a => isUnd(a) || a === null; /**@param {any} a @return {a is SVGElement} */ const isSvg = a => isBrowser && a instanceof SVGElement; /**@param {any} a @return {Boolean} */ const isHex = a => hexTestRgx.test(a); /**@param {any} a @return {Boolean} */ const isRgb = a => stringStartsWith(a, 'rgb'); /**@param {any} a @return {Boolean} */ const isHsl = a => stringStartsWith(a, 'hsl'); /**@param {any} a @return {Boolean} */ // Make sure boxShadow syntax like 'rgb(255, 0, 0) 0px 0px 6px 0px' is not a valid color type const isCol = a => isHex(a) || ((isRgb(a) || isHsl(a)) && (a[a.length - 1] === ')' || !validRgbHslRgx.test(a))); /**@param {any} a @return {Boolean} */ const isKey = a => !globals.defaults.hasOwnProperty(a); // SVG // Consider the following as CSS animation // CSS opacity animation has better default values (opacity: 1 instead of 0)) // rotate is more commonly intended to be used as a transform const svgCssReservedProperties = ['opacity', 'rotate', 'overflow', 'color']; /** * @param {Target} el * @param {String} propertyName * @return {Boolean} */ const isValidSVGAttribute = (el, propertyName) => { if (svgCssReservedProperties.includes(propertyName)) return false; if (el.getAttribute(propertyName) || propertyName in el) { if (propertyName === 'scale') { // Scale const elParentNode = /** @type {SVGGeometryElement} */(/** @type {DOMTarget} */(el).parentNode); // Only consider scale as a valid SVG attribute on filter element return elParentNode && elParentNode.tagName === 'filter'; } return true; } }; // Number /** * @param {Number|String} str * @return {Number} */ const parseNumber = str => isStr(str) ? parseFloat(/** @type {String} */(str)) : /** @type {Number} */(str); // Math const pow = Math.pow; const sqrt = Math.sqrt; const sin = Math.sin; const cos = Math.cos; const abs = Math.abs; const exp = Math.exp; const ceil = Math.ceil; const floor = Math.floor; const asin = Math.asin; const max = Math.max; const atan2 = Math.atan2; const PI = Math.PI; const _round = Math.round; /** * Clamps a value between min and max bounds * * @param {Number} v - Value to clamp * @param {Number} min - Minimum boundary * @param {Number} max - Maximum boundary * @return {Number} */ const clamp = (v, min, max) => v < min ? min : v > max ? max : v; const powCache = {}; /** * Rounds a number to specified decimal places * * @param {Number} v - Value to round * @param {Number} decimalLength - Number of decimal places * @return {Number} */ const round = (v, decimalLength) => { if (decimalLength < 0) return v; if (!decimalLength) return _round(v); let p = powCache[decimalLength]; if (!p) p = powCache[decimalLength] = 10 ** decimalLength; return _round(v * p) / p; }; /** * Snaps a value to nearest increment or array value * * @param {Number} v - Value to snap * @param {Number|Array<Number>} increment - Step size or array of snap points * @return {Number} */ const snap = (v, increment) => isArr(increment) ? increment.reduce((closest, cv) => (abs(cv - v) < abs(closest - v) ? cv : closest)) : increment ? _round(v / increment) * increment : v; /** * Linear interpolation between two values * * @param {Number} start - Starting value * @param {Number} end - Ending value * @param {Number} factor - Interpolation factor in the range [0, 1] * @return {Number} The interpolated value */ const lerp = (start, end, factor) => start + (end - start) * factor; /** * Replaces infinity with maximum safe value * * @param {Number} v - Value to check * @return {Number} */ const clampInfinity = v => v === Infinity ? maxValue : v === -Infinity ? -maxValue : v; /** * Normalizes time value with minimum threshold * * @param {Number} v - Time value to normalize * @return {Number} */ const normalizeTime = v => v <= minValue ? minValue : clampInfinity(round(v, 11)); // Arrays /** * @template T * @param {T[]} a * @return {T[]} */ const cloneArray = a => isArr(a) ? [ ...a ] : a; // Objects /** * @template T * @template U * @param {T} o1 * @param {U} o2 * @return {T & U} */ const mergeObjects = (o1, o2) => { const merged = /** @type {T & U} */({ ...o1 }); for (let p in o2) { const o1p = /** @type {T & U} */(o1)[p]; merged[p] = isUnd(o1p) ? /** @type {T & U} */(o2)[p] : o1p; } return merged; }; // Linked lists /** * @param {Object} parent * @param {Function} callback * @param {Boolean} [reverse] * @param {String} [prevProp] * @param {String} [nextProp] * @return {void} */ const forEachChildren = (parent, callback, reverse, prevProp = '_prev', nextProp = '_next') => { let next = parent._head; let adjustedNextProp = nextProp; if (reverse) { next = parent._tail; adjustedNextProp = prevProp; } while (next) { const currentNext = next[adjustedNextProp]; callback(next); next = currentNext; } }; /** * @param {Object} parent * @param {Object} child * @param {String} [prevProp] * @param {String} [nextProp] * @return {void} */ const removeChild = (parent, child, prevProp = '_prev', nextProp = '_next') => { const prev = child[prevProp]; const next = child[nextProp]; prev ? prev[nextProp] = next : parent._head = next; next ? next[prevProp] = prev : parent._tail = prev; child[prevProp] = null; child[nextProp] = null; }; /** * @param {Object} parent * @param {Object} child * @param {Function} [sortMethod] * @param {String} prevProp * @param {String} nextProp * @return {void} */ const addChild = (parent, child, sortMethod, prevProp = '_prev', nextProp = '_next') => { let prev = parent._tail; while (prev && sortMethod && sortMethod(prev, child)) prev = prev[prevProp]; const next = prev ? prev[nextProp] : parent._head; prev ? prev[nextProp] = child : parent._head = child; next ? next[prevProp] = child : parent._tail = child; child[prevProp] = prev; child[nextProp] = next; }; export { PI, _round, abs, addChild, asin, atan2, ceil, clamp, clampInfinity, cloneArray, cos, exp, floor, forEachChildren, isArr, isCol, isFnc, isHex, isHsl, isKey, isNil, isNum, isObj, isRgb, isStr, isSvg, isUnd, isValidSVGAttribute, lerp, max, mergeObjects, normalizeTime, now, parseNumber, pow, removeChild, round, sin, snap, sqrt, stringStartsWith, toLowerCase };