/
githubmirror
/
anime
Обзор
Документация
Войти
/
githubmirror
/
anime
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
4.0.0
src/eases.js
319 строк
10 KB
Julian Garnier
v4.0.0
03 апр 2025, 16:31
03 апр 2025, 16:31
66cfea7
Код
Авторство
О чём код?
/// <reference path='./types.js' /> import { minValue, emptyString, } from './consts.js'; import { isUnd, isStr, isFnc, parseNumber, clamp, sqrt, cos, sin, ceil, floor, abs, asin, PI, pow, } from './helpers.js'; /** @type {EasingFunction} */ export const none = t => t; // Cubic Bezier solver adapted from https://github.com/gre/bezier-ease © Gaëtan Renaudeau /** * @param {Number} aT * @param {Number} aA1 * @param {Number} aA2 * @return {Number} */ const calcBezier = (aT, aA1, aA2) => (((1 - 3 * aA2 + 3 * aA1) * aT + (3 * aA2 - 6 * aA1)) * aT + (3 * aA1)) * aT; /** * @param {Number} aX * @param {Number} mX1 * @param {Number} mX2 * @return {Number} */ const binarySubdivide = (aX, mX1, mX2) => { let aA = 0, aB = 1, currentX, currentT, i = 0; do { currentT = aA + (aB - aA) / 2; currentX = calcBezier(currentT, mX1, mX2) - aX; if (currentX > 0) { aB = currentT; } else { aA = currentT; } } while (abs(currentX) > .0000001 && ++i < 100); return currentT; } /** * @param {Number} [mX1] * @param {Number} [mY1] * @param {Number} [mX2] * @param {Number} [mY2] * @return {EasingFunction} */ export const cubicBezier = (mX1 = 0.5, mY1 = 0.0, mX2 = 0.5, mY2 = 1.0) => (mX1 === mY1 && mX2 === mY2) ? none : t => t === 0 || t === 1 ? t : calcBezier(binarySubdivide(t, mX1, mX2), mY1, mY2); /** * Steps ease implementation https://developer.mozilla.org/fr/docs/Web/CSS/transition-timing-function * Only covers 'end' and 'start' jumpterms * @param {Number} steps * @param {Boolean} [fromStart] * @return {EasingFunction} */ export const steps = (steps = 10, fromStart) => { const roundMethod = fromStart ? ceil : floor; return t => roundMethod(clamp(t, 0, 1) * steps) * (1 / steps); } /** * Without parameters, the linear function creates a non-eased transition. * Parameters, if used, creates a piecewise linear easing by interpolating linearly between the specified points. * @param {...String|Number} [args] - Points * @return {EasingFunction} */ const linear = (...args) => { const argsLength = args.length; if (!argsLength) return none; const totalPoints = argsLength - 1; const firstArg = args[0]; const lastArg = args[totalPoints]; const xPoints = [0]; const yPoints = [parseNumber(firstArg)]; for (let i = 1; i < totalPoints; i++) { const arg = args[i]; const splitValue = isStr(arg) ? /** @type {String} */(arg).trim().split(' ') : [arg]; const value = splitValue[0]; const percent = splitValue[1]; xPoints.push(!isUnd(percent) ? parseNumber(percent) / 100 : i / totalPoints); yPoints.push(parseNumber(value)); } yPoints.push(parseNumber(lastArg)); xPoints.push(1); return function easeLinear(t) { for (let i = 1, l = xPoints.length; i < l; i++) { const currentX = xPoints[i]; if (t <= currentX) { const prevX = xPoints[i - 1]; const prevY = yPoints[i - 1]; return prevY + (yPoints[i] - prevY) * (t - prevX) / (currentX - prevX); } } return yPoints[yPoints.length - 1]; } } /** * Generate random steps * @param {Number} [length] - The number of steps * @param {Number} [randomness] - How strong the randomness is * @return {EasingFunction} */ const irregular = (length = 10, randomness = 1) => { const values = [0]; const total = length - 1; for (let i = 1; i < total; i++) { const previousValue = values[i - 1]; const spacing = i / total; const segmentEnd = (i + 1) / total; const randomVariation = spacing + (segmentEnd - spacing) * Math.random(); // Mix the even spacing and random variation based on the randomness parameter const randomValue = spacing * (1 - randomness) + randomVariation * randomness; values.push(clamp(randomValue, previousValue, 1)); } values.push(1); return linear(...values); } // Easing functions adapted from http://www.robertpenner.com/ease © Robert Penner /** * @callback PowerEasing * @param {Number|String} [power=1.675] * @return {EasingFunction} */ /** * @callback BackEasing * @param {Number|String} [overshoot=1.70158] * @return {EasingFunction} */ /** * @callback ElasticEasing * @param {Number|String} [amplitude=1] * @param {Number|String} [period=.3] * @return {EasingFunction} */ /** * @callback EaseFactory * @param {Number|String} [paramA] * @param {Number|String} [paramB] * @return {EasingFunction|Number} */ /** @typedef {PowerEasing|BackEasing|ElasticEasing} EasesFactory */ const halfPI = PI / 2; const doublePI = PI * 2; /** @type {PowerEasing} */ export const easeInPower = (p = 1.68) => t => pow(t, +p); /** @type {Record<String, EasesFactory|EasingFunction>} */ const easeInFunctions = { [emptyString]: easeInPower, Quad: easeInPower(2), Cubic: easeInPower(3), Quart: easeInPower(4), Quint: easeInPower(5), /** @type {EasingFunction} */ Sine: t => 1 - cos(t * halfPI), /** @type {EasingFunction} */ Circ: t => 1 - sqrt(1 - t * t), /** @type {EasingFunction} */ Expo: t => t ? pow(2, 10 * t - 10) : 0, /** @type {EasingFunction} */ Bounce: t => { let pow2, b = 4; while (t < ((pow2 = pow(2, --b)) - 1) / 11); return 1 / pow(4, 3 - b) - 7.5625 * pow((pow2 * 3 - 2) / 22 - t, 2); }, /** @type {BackEasing} */ Back: (overshoot = 1.70158) => t => (+overshoot + 1) * t * t * t - +overshoot * t * t, /** @type {ElasticEasing} */ Elastic: (amplitude = 1, period = .3) => { const a = clamp(+amplitude, 1, 10); const p = clamp(+period, minValue, 2); const s = (p / doublePI) * asin(1 / a); const e = doublePI / p; return t => t === 0 || t === 1 ? t : -a * pow(2, -10 * (1 - t)) * sin(((1 - t) - s) * e); } } /** * @callback EaseType * @param {EasingFunction} Ease * @return {EasingFunction} */ /** @type {Record<String, EaseType>} */ export const easeTypes = { in: easeIn => t => easeIn(t), out: easeIn => t => 1 - easeIn(1 - t), inOut: easeIn => t => t < .5 ? easeIn(t * 2) / 2 : 1 - easeIn(t * -2 + 2) / 2, outIn: easeIn => t => t < .5 ? (1 - easeIn(1 - t * 2)) / 2 : (easeIn(t * 2 - 1) + 1) / 2, } /** * @param {String} string * @param {Record<String, EasesFactory|EasingFunction>} easesFunctions * @param {Object} easesLookups * @return {EasingFunction} */ export const parseEaseString = (string, easesFunctions, easesLookups) => { if (easesLookups[string]) return easesLookups[string]; if (string.indexOf('(') <= -1) { const hasParams = easeTypes[string] || string.includes('Back') || string.includes('Elastic'); const parsedFn = /** @type {EasingFunction} */(hasParams ? /** @type {EasesFactory} */(easesFunctions[string])() : easesFunctions[string]); return parsedFn ? easesLookups[string] = parsedFn : none; } else { const split = string.slice(0, -1).split('('); const parsedFn = /** @type {EasesFactory} */(easesFunctions[split[0]]); return parsedFn ? easesLookups[string] = parsedFn(...split[1].split(',')) : none; } } /** * @typedef {Object} EasesFunctions * @property {typeof linear} linear * @property {typeof irregular} irregular * @property {typeof steps} steps * @property {typeof cubicBezier} cubicBezier * @property {PowerEasing} in * @property {PowerEasing} out * @property {PowerEasing} inOut * @property {PowerEasing} outIn * @property {EasingFunction} inQuad * @property {EasingFunction} outQuad * @property {EasingFunction} inOutQuad * @property {EasingFunction} outInQuad * @property {EasingFunction} inCubic * @property {EasingFunction} outCubic * @property {EasingFunction} inOutCubic * @property {EasingFunction} outInCubic * @property {EasingFunction} inQuart * @property {EasingFunction} outQuart * @property {EasingFunction} inOutQuart * @property {EasingFunction} outInQuart * @property {EasingFunction} inQuint * @property {EasingFunction} outQuint * @property {EasingFunction} inOutQuint * @property {EasingFunction} outInQuint * @property {EasingFunction} inSine * @property {EasingFunction} outSine * @property {EasingFunction} inOutSine * @property {EasingFunction} outInSine * @property {EasingFunction} inCirc * @property {EasingFunction} outCirc * @property {EasingFunction} inOutCirc * @property {EasingFunction} outInCirc * @property {EasingFunction} inExpo * @property {EasingFunction} outExpo * @property {EasingFunction} inOutExpo * @property {EasingFunction} outInExpo * @property {EasingFunction} inBounce * @property {EasingFunction} outBounce * @property {EasingFunction} inOutBounce * @property {EasingFunction} outInBounce * @property {BackEasing} inBack * @property {BackEasing} outBack * @property {BackEasing} inOutBack * @property {BackEasing} outInBack * @property {ElasticEasing} inElastic * @property {ElasticEasing} outElastic * @property {ElasticEasing} inOutElastic * @property {ElasticEasing} outInElastic */ export const eases = (/*#__PURE__*/ (() => { const list = { linear, irregular, steps, cubicBezier }; for (let type in easeTypes) { for (let name in easeInFunctions) { const easeIn = easeInFunctions[name]; const easeType = easeTypes[type]; list[type + name] = /** @type {EasesFactory|EasingFunction} */( name === emptyString || name === 'Back' || name === 'Elastic' ? (a, b) => easeType(/** @type {EasesFactory} */(easeIn)(a, b)) : easeType(/** @type {EasingFunction} */(easeIn)) ); } } return /** @type {EasesFunctions} */(list); })()); /** @type {Record<String, EasingFunction>} */ const JSEasesLookups = { linear: none }; /** * @param {EasingParam} ease * @return {EasingFunction} */ export const parseEasings = ease => isFnc(ease) ? ease : isStr(ease) ? parseEaseString(/** @type {String} */(ease), eases, JSEasesLookups) : none;