animejs
Version:
JavaScript animation engine
262 lines (230 loc) • 7.78 kB
JavaScript
/**
* 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 };