UNPKG

animatry

Version:
1,226 lines (1,219 loc) 82.6 kB
let globalOptions = {}; function setGlobalOptions(options) { globalOptions = { ...globalOptions, ...options }; } const controllerFunctions = () => { return { onUpdate: () => { }, onChange: () => { }, onStart: () => { }, onComplete: () => { }, onReverseStart: () => { }, onReverseComplete: () => { }, onRepeat: () => { }, }; }; const controllerOptions = () => { return { id: undefined, delay: 0, delayRecharge: false, duration: 1, repeat: 0, iterationDelay: 0, alternate: false, paused: false, ease: undefined, alternateEase: undefined, playhead: 0, iteration: 0, reversed: false, timeScale: 1, at: undefined, preRender: false, stagger: 0, keyframes: undefined, backwards: false, }; }; const controllerSettings = () => { return Object.assign({}, controllerFunctions(), controllerOptions(), globalOptions); }; var _a; class Ease { static steps(numSteps = 3) { const stepSize = 1 / numSteps; const halfStepSize = stepSize / 2; return (x) => { const centeredX = x + halfStepSize; const stepIndex = Math.floor(centeredX / stepSize); return stepIndex * stepSize; }; } static cubicBezier(p1x, p1y, p2x, p2y) { function cubic(t, a1, a2) { const c = 3 * a1; const b = 3 * (a2 - a1) - c; const a = 1 - c - b; return ((a * t + b) * t + c) * t; } function derivativeCubic(t, a1, a2) { const c = 3 * a1; const b = 3 * (a2 - a1) - c; const a = 1 - c - b; return (3 * a * t + 2 * b) * t + c; } return function (x) { let t = x; for (let i = 0; i < 5; i++) { const xEstimate = cubic(t, p1x, p2x); const derivative = derivativeCubic(t, p1x, p2x); if (Math.abs(xEstimate - x) < 1e-5) return cubic(t, p1y, p2y); t -= (xEstimate - x) / derivative; } return cubic(t, p1y, p2y); }; } ; static parse(ease) { if (typeof ease == 'string') { const easeFunctionMatch = ease.match(/(\w+)\(([^)]*)\)/); if (easeFunctionMatch) { const functionName = easeFunctionMatch[1]; const argsString = easeFunctionMatch[2].trim(); const args = argsString ? argsString.split(',').map(arg => parseFloat(arg)) : []; if (this[functionName] !== undefined) { return this[functionName](...args); } animatry.warn(`ease '${functionName}' is not defined.`); return _a.linear(); } if (this[ease] == undefined) { animatry.warn(`ease '${ease}' is not defined.`); return _a.linear(); } return this[ease](); } return ease; } } _a = Ease; Ease.none = () => (x) => x; Ease.linear = () => (x) => x; Ease.powerIn = (power = 1) => (x) => Math.pow(x, power + 1); Ease.powerOut = (power = 1) => (x) => 1 - Math.pow(1 - x, power + 1); Ease.powerInOut = (power = 1) => (x) => x < 0.5 ? 0.5 * Math.pow(2 * x, power + 1) : 1 - 0.5 * Math.pow(2 * (1 - x), power + 1); Ease.bounceIn = () => (x) => 1 - _a.bounceOut()(1 - x); Ease.bounceOut = () => (x) => { if (x < 1 / 2.75) { return 7.5625 * x * x; } else if (x < 2 / 2.75) { return 7.5625 * (x -= 1.5 / 2.75) * x + 0.75; } else if (x < 2.5 / 2.75) { return 7.5625 * (x -= 2.25 / 2.75) * x + 0.9375; } else { return 7.5625 * (x -= 2.625 / 2.75) * x + 0.984375; } }; Ease.bounceInOut = () => (x) => x < 0.5 ? 0.5 * _a.bounceIn()(x * 2) : 0.5 * _a.bounceOut()(x * 2 - 1) + 0.5; Ease.elasticIn = (frequency = 6) => (x) => 1 - _a.elasticOut(frequency)(1 - x); Ease.elasticOut = (frequency = 6) => (x) => { if (x < 0) return 0; if (x > 1) return 1; const decay = Math.pow(0.025, x); const oscillation = Math.pow(x, frequency ** 2); const smoothing = Math.pow(1 - x, 1); return 1 + Math.sin(x * frequency * Math.PI - Math.PI / 2) * decay * (1 - oscillation) * smoothing; }; Ease.elasticInOut = (frequency = 6) => (x) => x < 0.5 ? _a.elasticIn(frequency)(x * 2) * 0.5 : _a.elasticOut(frequency)(x * 2 - 1) * 0.5 + 0.5; Ease.backIn = (magnitude = 1.70158) => (x) => x * x * ((magnitude + 1) * x - magnitude); Ease.backOut = (magnitude = 1.70158) => (x) => 1 + ((x - 1) ** 2 * ((magnitude + 1) * (x - 1) + magnitude)); Ease.backInOut = (magnitude = 1.70158) => (x) => x < 0.5 ? 0.5 * _a.backIn(magnitude)(x * 2) : 0.5 * _a.backOut(magnitude)(x * 2 - 1) + 0.5; class Controller { constructor(options) { this.delayProgress = 0; this.iterationDelayProgress = 0; this.isPaused = false; this.isReversed = false; this.playhead = 0; this.frame = -1; this.tick = 0; this.initialized = false; this.options = Object.assign(controllerSettings(), options); this.options.ease = Ease.parse(this.options.ease); this.options.alternateEase = Ease.parse(this.options.alternateEase); this.playhead = this.options.playhead; this.setReversed(this.options.reversed); Promise.resolve().then(() => { if (!this.getParent() && !this.options.paused && !this.isPaused) this.play(); }); } _render() { if (this.parent != undefined) return; const now = performance.now(); const deltaTime = (this.isReversed ? this.tick - now : now - this.tick) * this.getTimeScale() / 1000; this.tick = now; if (!this.isReversed && this.delayProgress < 1 || this.options.delayRecharge && this.isReversed && this.getTotalProgress() == 0) { this.setDelayProgress(this.getDelay() > 0 ? (this.getDelayProgress() + deltaTime / this.getDelay()) : 1); } else { this.setTotalProgress(this.getTotalProgress() + deltaTime / (this.getTotalDuration() || 1e-8)); } if (!this.isPaused) { this.frame = requestAnimationFrame(this._render.bind(this)); } } play(seek = undefined) { if (this.playhead == 1) return; this.isPaused = false; this.tick = performance.now(); this.isReversed = false; if (this.frame == -1) this._render(); if (seek != undefined) this.seek(seek); } pause() { this.isPaused = true; cancelAnimationFrame(this.frame); this.frame = -1; if (this.getDelayProgress() != 1) { this.setDelayProgress(0); } } reverse() { if (this.playhead == 0) return; this.isPaused = false; this.tick = performance.now(); this.isReversed = true; if (this.frame == -1) this._render(); } continue() { if (this.isReversed) { this.reverse(); } else { this.play(); } } restart() { this.pause(); this.setTotalProgress(0); this.play(); } reset() { this.setDelayProgress(0); this.setTotalProgress(0); } seek(elapsed) { this.setTotalElapsed(elapsed); } complete() { this.setTotalProgress(1); } lastPlay() { this.setIteration(this.getReversed() ? 0 : this.getRepeat()); } getInitialized() { return this.options.preRender || this.initialized; } setInitialized() { this.initialized = true; } getParent() { return this.parent; } setParent(parent) { this.parent = parent; } getId() { var _a; return (_a = this.options.id) !== null && _a !== void 0 ? _a : ''; } setId(id) { this.options.id = id; } getDuration() { return Math.min(this.options.duration, 10 ** 8); } setDuration(duration, keepProgress = true) { if (!keepProgress) { this.setProgress((this.getProgress() / duration) * this.getDuration()); } this.options.duration = duration; return this; } getProgress() { return Controller.getProgress(this, this.playhead); } setProgress(progress) { this.setTotalElapsed(this.getIteration() * (this.getDuration() + this.getIterationDelay()) + (this.isAlternating() ? 1 - progress : progress) * this.getDuration()); return this; } getElapsed() { return this.getProgress() * this.getDuration(); } setElapsed(elapsed) { this.setProgress(this.getDuration() == 0 ? elapsed : elapsed / this.getDuration()); return this; } getTotalProgress() { return this.playhead; } setTotalProgress(totalProgress, events = true) { totalProgress = animatry.clamp(totalProgress, 0, 1); if (this.getReversed()) { if (totalProgress != 0) this.isPaused = false; } else { if (totalProgress != 1) this.isPaused = false; } if (events) { if (this.getTotalProgress() == 0 && totalProgress > 0) this.options.onStart(this); if (this.getTotalProgress() < 1 && totalProgress == 1) this.options.onComplete(this); if (this.getTotalProgress() == 1 && totalProgress < 1) this.options.onReverseStart(this); if (this.getTotalProgress() > 0 && totalProgress == 0) this.options.onReverseComplete(this); if (this.getIteration() != Controller.getIteration(this, totalProgress)) this.options.onRepeat(this); } this.playhead = totalProgress; if (this.isPlaying()) { if (this.getReversed()) { if (totalProgress == 0) { if (!this.options.delayRecharge || this.getDelayProgress() == 0) { this.pause(); } } } else { if (totalProgress == 1) { this.pause(); } } } } getTotalElapsed() { return this.getTotalProgress() * (this.getTotalDuration() || 1e-8); } setTotalElapsed(totalElapsed) { this.setTotalProgress(totalElapsed / (this.getTotalDuration() || 1e-8)); } getDelay() { return this.options.delay; } setDelay(delay, restartDelay = undefined) { if (restartDelay == undefined) { restartDelay = this.delayProgress !== 1; } const delayBefore = this.options.delay; this.options.delay = delay; if (restartDelay) { this.delayProgress = 0; return this; } this.delayProgress = animatry.clamp((this.delayProgress / delay) * delayBefore, 0, delay); return this; } getDelayProgress() { if (this.getDelay() == 0) return 1; return this.delayProgress; } setDelayProgress(delayProgress) { this.delayProgress = animatry.clamp(delayProgress, 0, 1); return this; } getDelayElapsed() { return this.getDelayProgress() * this.getDelay(); } setDelayElapsed(delayElapsed) { this.setDelayProgress(delayElapsed / this.getDelay()); return this; } getReversed() { return this.isReversed; } setReversed(reversed) { this.isReversed = reversed; } getRepeat() { const { repeat, duration } = this.options; if (this.getDuration() == 0) return repeat == -1 ? 10 ** 8 : repeat; return Math.floor(repeat == -1 ? 10 ** 8 / duration : Math.min(repeat, 10 ** 8 / duration)); } setRepeat(repeat, keepIterations = true) { const iteration = this.getIteration(); const progress = this.getProgress(); this.options.repeat = repeat; this.setIteration(iteration); this.setProgress(progress); if (!keepIterations) this.setIteration(0); if (iteration > repeat) this.setTotalProgress(1); return this; } getAlternate() { return this.options.alternate; } setAlternate(alternate, smoothJump = true) { const wasAlternating = this.isAlternating(); if (this.getAlternate() != alternate) { this.options.alternate = alternate; if (smoothJump && wasAlternating !== this.isAlternating()) { this.setProgress(1 - this.getProgress()); } } return this; } getIteration() { if (this.getTotalDuration() == 0) { return Math.round(this.getTotalProgress()) * this.getRepeat(); } if (this.getTotalProgress() == 1) { return this.getRepeat(); } const duration = this.getDuration() + this.getIterationDelay(); const totalElapsed = this.getTotalDuration() * this.playhead; return Math.floor(totalElapsed / duration); } setIteration(iteration, smoothJump = true) { if (smoothJump && this.getAlternate() && this.getIteration() % 2 != iteration % 2) { this.setProgress(1 - this.getProgress()); } this.setTotalElapsed(iteration * (this.getDuration() + this.getIterationDelay()) + (this.isAlternating() ? this.getDuration() - this.getElapsed() : this.getElapsed())); return this; } getIterationDelay() { return this.options.iterationDelay; } setIterationDelay(iterationDelay) { const progress = this.getProgress(); const iteration = this.getIteration(); this.options.iterationDelay = iterationDelay; this.setIteration(iteration); this.setProgress(progress); return this; } getIterationDelayProgress() { return this.iterationDelayProgress; } setIterationDelayProgress(iterationDelayProgress) { this.iterationDelayProgress = animatry.clamp(iterationDelayProgress, 0, 1); return this; } getTimeScale() { return this.options.timeScale; } setTimeScale(timeScale) { this.options.timeScale = timeScale; } getTotalDuration() { return (this.getRepeat() + 1) * this.getDuration() + this.getRepeat() * this.getIterationDelay(); } getEasedProgress() { return Controller.getEasedProgress(this, this.playhead); } getEasedElapsed() { return this.getEasedProgress() * this.getDuration(); } isAlternating() { return Controller.isAlternating(this, this.playhead); } isPlaying() { return !this.isPaused; } onChange(callback) { this.options.onChange = callback; } onUpdate(callback) { this.options.onUpdate = callback; } onStart(callback) { this.options.onStart = callback; } onRepeat(callback) { this.options.onRepeat = callback; } onComplete(callback) { this.options.onComplete = callback; } onReverseStart(callback) { this.options.onReverseStart = callback; } onReverseComplete(callback) { this.options.onReverseComplete = callback; } static getProgress(instance, ph) { if (instance.getDuration() == 0) { if (instance.getRepeat() > 0) { const value = Math.ceil(animatry.clamp(ph * instance.getRepeat(), 0, 1)); return (Controller.isAlternating(instance, ph) ? 1 - value : value); } return Math.round(ph); } if (ph == 1 && instance.getIterationDelay() == 0) { if (instance.getAlternate()) { if (Controller.isAlternating(instance, ph)) { return 0; } } return 1; } const duration = instance.getDuration() + instance.getIterationDelay(); const totalElapsed = (instance.getTotalDuration() || 1e-8) * ph; let elapsed = totalElapsed % duration; let progress = animatry.clamp(elapsed / instance.getDuration(), 0, 1); return (Controller.isAlternating(instance, ph) ? 1 - progress : progress); } static getEasedProgress(instance, ph) { var _a; const progress = Controller.getProgress(instance, ph); const easeFunction = (Controller.isAlternating(instance, instance.options.backwards ? 1 - ph : ph) && instance.options.alternateEase !== undefined) ? instance.options.alternateEase : ((_a = instance.options.ease) !== null && _a !== void 0 ? _a : Ease.powerInOut()); return instance.options.backwards ? 1 - easeFunction(progress) : easeFunction(progress); } static getIteration(instance, ph) { if (ph == 0) { return Math.round(ph) * instance.getRepeat(); } if (ph == 1) { return instance.getRepeat(); } const duration = instance.getDuration() + instance.getIterationDelay(); const totalElapsed = (instance.getTotalDuration() || 1e-8) * ph; return Math.floor(totalElapsed / duration); } static isAlternating(instance, ph) { return instance.getAlternate() && Controller.getIteration(instance, ph) % 2 == 1; } } const _timestamp = (previous, time, labels, object = undefined) => { var _a, _b, _c, _d, _e; let prevStart = (_a = previous === null || previous === void 0 ? void 0 : previous.getStartTime()) !== null && _a !== void 0 ? _a : 0; let prevEnd = (_b = previous === null || previous === void 0 ? void 0 : previous.getEndTime()) !== null && _b !== void 0 ? _b : 0; if (typeof time === 'number') return time !== null && time !== void 0 ? time : prevEnd; if (!time && prevEnd >= 10 ** 8) { animatry.warn(`Placing a tween after someting infinite will not work.`); } if (!time) return prevEnd; const [, label, calc, number, unit] = time.match(/(^[A-Za-z][\w]*|^<|^>)?([+-]=)?([+-]?\d*\.?\d+)?(\w*|%)?$/) || []; if ((unit && !number && !label) || (unit && !number)) { animatry.warn(`invalid format '${time}'`); return 0; } const hasLabel = /^[A-Za-z]/.test(time); if (hasLabel && labels[label] == undefined) label ? animatry.warn(`label '${label}' not defined`) : animatry.warn(`invalid format '${time}'`); const start = hasLabel ? labels[label] : /^</.test(time) ? prevStart : prevEnd; let additive = hasLabel || /^[<>]/.test(time) || unit === '%' || /[+-]=/.test(calc); let nmbr = parseFloat(number) || 0; if (calc === null || calc === void 0 ? void 0 : calc.startsWith('-')) nmbr *= -1; let unitMultiplier; if (unit === '%') { if (!object) { unitMultiplier = (((_d = (_c = previous === null || previous === void 0 ? void 0 : previous.getAnimation()) === null || _c === void 0 ? void 0 : _c.getParent()) === null || _d === void 0 ? void 0 : _d.getDuration()) || 0) / 100; } else if (/[+-]=/.test(calc) || (hasLabel && !calc)) { unitMultiplier = ((object === null || object === void 0 ? void 0 : object.getTotalDuration()) || 1e-8) / 100; } else { unitMultiplier = (((_e = previous === null || previous === void 0 ? void 0 : previous.getAnimation()) === null || _e === void 0 ? void 0 : _e.getTotalDuration()) || 1e-8) / 100; } } else if (unit === 'ms') { unitMultiplier = 1 / 1000; } else { unitMultiplier = 1; } const res = (additive ? start : 0) + nmbr * unitMultiplier; return isNaN(res) ? prevEnd : res; }; class Attached { constructor(animation, time) { this.animation = animation; this.time = time; } getAnimation() { return this.animation; } getStartTime() { return this.time + this.animation.getDelay(); } getEndTime() { return this.getStartTime() + this.animation.getTotalDuration() / this.animation.getTimeScale() + (this.animation.getTotalDuration() == 0 ? 1e-8 : 0); } } class Timeline extends Controller { constructor(options = {}) { super(Object.assign({ duration: 0, ease: Ease.none() }, options)); this.attacheds = []; this.labels = {}; this.controller = options; } getController() { return this.controller; } setTotalProgress(totalProgress, events = true) { totalProgress = animatry.clamp(totalProgress, 0, 1); const easedElapsed = Controller.getEasedProgress(this, totalProgress) * this.getDuration(); let reversed = totalProgress < this.getTotalProgress() !== this.isAlternating(); const attachedList = reversed ? this.attacheds.slice().reverse() : this.attacheds; const shouldInitialize = this.options.preRender; attachedList.forEach(attached => { const animation = attached.getAnimation(); let elapsed = easedElapsed - attached.getStartTime(); const isConditionMet = reversed ? easedElapsed < attached.getEndTime() : easedElapsed > attached.getStartTime(); if (shouldInitialize || isConditionMet) { animation.setInitialized(); } if (isConditionMet) { if (animation.getIteration() > 0) { if (animation.isAlternating()) { animation.setTotalProgress(1, elapsed >= attached.getEndTime()); } } animation.setTotalElapsed(elapsed); } }); super.setTotalProgress(totalProgress, events); } _updateDuration(crop = false) { let maxTime = 0; if (!crop) { maxTime = this.getDuration(); } this.attacheds.forEach(attached => { if (attached.getEndTime() > maxTime) { maxTime = attached.getEndTime(); } }); this.setDuration(maxTime, false); } label(name, time = undefined) { if (!/^[A-Za-z][\w-]*$/.test(name)) { animatry.warn(`'${name}' includes invalid characters`); return this; } this.labels[name] = _timestamp(this.attacheds[this.attacheds.length - 1], time, this.labels); return this; } add(object, time = undefined) { var _a, _b; if (!(object instanceof Controller)) { animatry.warn(`invalid object '${object}' did you intend to use .to() instead of .add() ?`); object = animatry.to(object, time); time = undefined; } let previous = this.attacheds[this.attacheds.length - 1]; const start = _timestamp(previous, (_b = (_a = time !== null && time !== void 0 ? time : object.options.at) !== null && _a !== void 0 ? _a : previous === null || previous === void 0 ? void 0 : previous.getEndTime()) !== null && _b !== void 0 ? _b : 0, this.labels, object); if (object.options.duration >= 10 ** 8 && (object.options.repeat != 0 || this.options.repeat != 0)) { object.setRepeat(0); this.setRepeat(0); } else if (this.options.repeat != 0 && object.options.repeat == -1) { this.setRepeat(0); } object.setParent(this); const attached = new Attached(object, start); this.attacheds.push(attached); this._updateDuration(); if (!this.options.paused) this.play(); return this; } play(seek = undefined) { this.attacheds.forEach(attached => { attached.getAnimation().play(); }); super.play(seek); } reverse() { this.attacheds.forEach(attached => { attached.getAnimation().reverse(); }); super.reverse(); } seek(elapsed) { if (typeof elapsed === 'string' && /^[A-Za-z]|^[<>]/.test(elapsed)) { let previous = this.attacheds[this.attacheds.length - 1]; elapsed = _timestamp(previous, elapsed, this.labels); } super.seek(elapsed); } fromTo(el, from, to, time = undefined) { return this.add(animatry.fromTo(el, from, to), time); } to(el, to, time = undefined) { return this.add(animatry.to(el, to), time); } from(el, from, time = undefined) { return this.add(animatry.from(el, from), time); } set(el, options, time = undefined) { return this.add(animatry.set(el, options), time); } } const keyframes = (tween, element, from, to) => { const timeline = new Timeline(); const options = tween.options.keyframes; const duration = tween.getDuration() || 1e-8; const preRender = tween.options.preRender; timeline.options.ease = options.ease ? Ease.parse(options.ease) : Ease.parse('none'); const fromValues = animatry.filterObjects(from, controllerSettings())[1]; if (options instanceof Array) { const frameCount = Object.keys(options).length; options.forEach((keyframe, index) => { timeline.add(new Tween(element, index == 0 ? fromValues : {}, { ...keyframe, at: index * duration / frameCount, duration: duration / frameCount, ...(preRender && index === 0 && { preRender: true }) })); }); } else { const keyframes = animatry.filterObjects(options, { ease: null, easeEach: null })[1]; const toValues = animatry.filterObjects(to, controllerSettings())[1]; if (Object.keys(keyframes)[0].includes('%')) { const percentageKey = Object.keys(keyframes).sort((a, b) => parseFloat(a) - parseFloat(b)); let lastAppeared = {}; const keyframeProperties = Array.from(new Set(Object.values(keyframes).flatMap(Object.keys))); const additionalKeyframes = { '0%': { ...Object.fromEntries(Object.entries(fromValues).filter(([key]) => keyframeProperties.includes(key))) }, '100%': { ...Object.fromEntries(Object.entries(toValues).filter(([key]) => keyframeProperties.includes(key))) } }; const combinedPercentages = [...new Set([ ...(Object.keys(additionalKeyframes['0%']).length > 0 ? ['0%'] : []), ...percentageKey, ...(Object.keys(additionalKeyframes['100%']).length > 0 ? ['100%'] : []) ])]; const combinedKeyframes = combinedPercentages.reduce((acc, percentage) => ({ ...acc, [percentage]: { ...(additionalKeyframes[percentage] || {}), ...(keyframes[percentage] || {}), }, }), {}); combinedPercentages.forEach(key => { const filtered = animatry.filterObjects(combinedKeyframes[key], controllerOptions()); const controller = filtered[0]; const frameOptions = filtered[1]; let numberKey = 1 / 100 * Number.parseFloat(key); Object.keys(frameOptions).forEach(property => { var _a, _b, _c, _d, _e; if (numberKey != 0) { timeline.add(new Tween(element, lastAppeared[property] != null ? { [property]: combinedKeyframes[(lastAppeared[property] * 100) + '%'][property] } : {}, { [property]: frameOptions[property], duration: (numberKey - ((_a = lastAppeared[property]) !== null && _a !== void 0 ? _a : 0)) * duration, ...controller, ease: (_d = (_c = (_b = controller['ease']) !== null && _b !== void 0 ? _b : options['easeEach']) !== null && _c !== void 0 ? _c : tween.options.ease) !== null && _d !== void 0 ? _d : Ease.powerInOut(), ...(preRender && !lastAppeared[property]) ? { preRender: true } : {}, }), ((_e = lastAppeared[property]) !== null && _e !== void 0 ? _e : 0) * duration); } lastAppeared[property] = numberKey; }); timeline.setDuration(duration); }); } else { const keys = Object.keys(keyframes); keys.forEach(key => { var _a; const values = keyframes[key]; const valueCount = values.length; const stepDuration = duration / Math.max(1, valueCount - 1); for (let index = 0; index < (valueCount === 1 ? 1 : valueCount - 1); index++) { timeline.add(new Tween(element, { [key]: values[index] }, { [key]: valueCount === 1 ? values[0] : values[index + 1], at: valueCount === 1 ? 0 : index * stepDuration, duration: valueCount === 1 ? duration / valueCount : stepDuration, ease: (_a = options['easeEach']) !== null && _a !== void 0 ? _a : tween.options.ease, ...(preRender && index === 0 ? { preRender: true } : {}), })); } }); } } return timeline; }; const regex = /^\s*([+-]=)?\s*([+-]?\d*\.?\d+)\s*([a-z%Q]+)?\s*$/; const isSignableNumber = (s) => regex.test(s); const combineSignedNumbers = (uc, prop, a, b) => { [a, b] = unifySignedNumberUnits(uc, prop, a, b); return [false, b[0] ? a[1] + b[1] : b[1], b[2]]; }; const toSignedNumber = (s) => { if (s == undefined) { animatry.warn('empty signed number'); return [false, 0, '']; } const match = s.toString().match(regex); if (!match) { animatry.warn('invalid signed number'); return [false, 0, '']; } const array = match === null || match === void 0 ? void 0 : match.slice(1); return [!!array[0], parseFloat(array[1]) * (/^-/.test(array[0]) ? -1 : 1), array[2]]; }; const unifySignedNumberUnits = (uc, prop, from, to) => { if (!from || !to) return [[false, 0, ''], [false, 0, '']]; const [, fromN, fromU] = from; const [toS, toN, toU] = to; if (CSS.supports(prop, '1deg') || CSS.supports('transform', `${prop}(1deg)`)) { return [ [false, (uc.getAngle(fromU !== null && fromU !== void 0 ? fromU : 'deg') / uc.getAngle(toU !== null && toU !== void 0 ? toU : 'deg')) * fromN, toU !== null && toU !== void 0 ? toU : 'deg'], [toS, toN, toU !== null && toU !== void 0 ? toU : 'deg'] ]; } else if (CSS.supports(prop, '1px') || CSS.supports('transform', `${prop}(1px)`) || /^(el[wh]|)$/.test(prop)) { return [ [false, (uc.getLength(prop, fromU !== null && fromU !== void 0 ? fromU : 'px') / uc.getLength(prop, toU !== null && toU !== void 0 ? toU : 'px')) * fromN, toU !== null && toU !== void 0 ? toU : 'px'], [toS, toN, toU !== null && toU !== void 0 ? toU : 'px'] ]; } else if (CSS.supports(prop, '1') || CSS.supports('transform', `${prop}(1)`)) { return [ [false, fromN, ''], to ]; } animatry.warn(`unit conversion failed with invalid property '${prop}'`); return [[false, 0, ''], to]; }; const convertSignedNumbers = (uc, prop, from, to, fallback) => { var _a; from = from !== null && from !== void 0 ? from : fallback; to = (_a = to !== null && to !== void 0 ? to : from) !== null && _a !== void 0 ? _a : fallback; return unifySignedNumberUnits(uc, prop, combineSignedNumbers(uc, prop, fallback, from), combineSignedNumbers(uc, prop, fallback, to)); }; const stringifySignedNumber = (sn) => { return `${sn[1]}${sn[2]}`; }; const validateColor = (c) => CSS.supports('color', c); const colorToRgba = (color) => { var _a, _b; let res = []; if (/^rgb(a)?\(/.test(color)) { const match = color.match(/^(?:rgb(?:a)?\()(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)(?:\s*,\s*(\d*\.?\d+))?\)/); if (!match) { console.warn('invalid rgba format'); } const [r, g, b, a = 1] = (match !== null && match !== void 0 ? match : [0, 0, 0, 0]).slice(1); res = [r, g, b, a]; } else if (/^hsl(a)?\(/.test(color)) { const match = color.match(/^(?:hsl(?:a)?\()(\d*\.?\d+)(?:%)?\s*,\s*(\d*\.?\d+)(?:%)?\s*,\s*(\d*\.?\d+)(?:%)?(?:\s*,\s*(\d*\.?\d+)(?:%)?)?\)/); let [h, s, l, a] = ((_a = match === null || match === void 0 ? void 0 : match.slice(1)) !== null && _a !== void 0 ? _a : [0, 0, 0, 0]).map(x => (x ? parseFloat(x) : 1)); s /= 100; l /= 100; const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs((h / 60) % 2 - 1)); const m1 = l - c / 2; const [r1, g1, b1] = h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x]; const [r, g, b] = [r1 + m1, g1 + m1, b1 + m1].map(val => Math.round(val * 255)); res = [r, g, b, a]; } else if (/^#/.test(color)) { const match = new RegExp(`^#(\\w(?:\\w)?)(\\w(?:\\w)?)(\\w(?:\\w)?)${color.length === 5 || color.length === 9 ? '(\\w(?:\\w)?)' : ''}$`).exec(color); if (!match) console.warn('invalid hex format'); const [r, g, b, a = 255] = ((_b = match === null || match === void 0 ? void 0 : match.slice(1)) !== null && _b !== void 0 ? _b : ['#fff']) .map(x => (x.length === 1 ? x + x : x)) .map(x => parseInt(x, 16)); res = [r, g, b, a / 255]; } else if (/^transparent/.test(color)) { res = [0, 0, 0, 0]; } else if (CSS.supports('color', color)) { const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; const ctx = canvas.getContext('2d', { willReadFrequently: true }); ctx.fillStyle = color; ctx.fillRect(0, 0, 1, 1); const d = ctx.getImageData(0, 0, 1, 1).data; res = [...d].slice(0, 3).concat(d[3] / 255); } return ['rgba(', res[0], ',', res[1], ',', res[2], ',', res[3], ')']; }; function unifyAllValues(uc, from, to) { let res1 = []; let res2 = []; from.forEach((fromItem, index) => { const toItem = to[index]; const comparisonResult = unifyValue(uc, '', fromItem, toItem, fromItem !== null && fromItem !== void 0 ? fromItem : toItem); res1.push(...stringifySignedNumber(comparisonResult[0])); res2.push(...stringifySignedNumber(comparisonResult[1])); res1.push(' '); res2.push(' '); }); return [res1, res2]; } const parseBorderOrOutline = (prop, b) => { var _a; const res = {}; b = b.replace('none', '#fff solid 0px'); ((_a = b.match(/(?:[^\s()]+|\([^\)]*\))+/g)) !== null && _a !== void 0 ? _a : []).forEach(d => { if (validateColor(d)) { res[`${prop}Color`] = d; } else if (/^(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)$/.test(d)) { res[`${prop}Style`] = d; } else if (isSignableNumber(d)) { res[`${prop}Width`] = d; } }); return res; }; const parseMarginOrPadding = (key, input) => { const [top, right = top, bottom = top, left = right] = input.toString().trim().split(/\s+/); return { [`${key}Top`]: top, [`${key}Right`]: right, [`${key}Bottom`]: bottom, [`${key}Left`]: left }; }; function parseBorderRadius(key, input) { const [h, v = ""] = input.split("/").map(part => part.trim().split(/\s+/)); const expand = (vals) => vals.length === 1 ? Array(4).fill(vals[0]) : vals.length === 2 ? [vals[0], vals[1], vals[0], vals[1]] : vals.length === 3 ? [vals[0], vals[1], vals[2], vals[1]] : vals.slice(0, 4); const horizontal = expand(h); const vertical = expand(Array.isArray(v) && v.length ? v : horizontal); return { borderTopLeftRadius: `${horizontal[0]} ${vertical[0]}`, borderTopRightRadius: `${horizontal[1]} ${vertical[1]}`, borderBottomRightRadius: `${horizontal[2]} ${vertical[2]}`, borderBottomLeftRadius: `${horizontal[3]} ${vertical[3]}`, }; } function disassambleBorderCornerRadius(value) { let res = value.split(' '); if (res.length == 1) { res = [...res, ...res]; } return res; } function parsePositionProperty(key, value) { let d = value.split(' '); let res = []; let maxValues = key === 'backgroundPosition' ? 2 : 3; d.forEach(o => { if (/^(left|right)$/.test(o)) { res[0] = o; } else if (/^(top|bottom)$/.test(o)) { res[1] = o; } else if (/^(\d+(\.\d+)?(px|%)?)$/.test(o) || o === 'center') { if (!res[0]) { res[0] = o; } else if (!res[1]) { res[1] = o; } else if (maxValues === 3 && !res[2]) { res[2] = o; } } }); [res[0], res[1], res[2]].forEach((o, i) => { var _a; if (i < maxValues && !o) { res[i] = i === 2 ? '0px' : d[0] !== res[i === 0 ? 1 : 0] ? d[0] : (_a = d[1]) !== null && _a !== void 0 ? _a : '50%'; } }); return res .slice(0, maxValues) .map(v => v .replace(/left|top/, '0%') .replace(/center/, '50%') .replace(/right|bottom/, '100%')); } function parseAndUnifyShadows(property, shadowString1, shadowString2) { function parseShadows(shadowString) { shadowString = shadowString.replace('none', 'black'); const flatShadows = []; shadowString.split(',').forEach(shadow => { const parts = shadow.trim().split(/\s+/); const color = parts.find(part => validateColor(part)); const [x = '0px', y = '0px', blur = '0px', spread = '0px'] = parts.filter(part => part !== color); if (property === "box-shadow") { flatShadows.push(x, y, blur, spread, color, ','); } else { flatShadows.push(x, y, blur, color, ','); } }); if (flatShadows[flatShadows.length - 1] === ',') { flatShadows.pop(); } return flatShadows; } const shadowArray1 = parseShadows(shadowString1); const shadowArray2 = parseShadows(shadowString2); const maxLength = Math.max(shadowArray1.length, shadowArray2.length); const defaultShadowBox = [',', "0px", "0px", "0px", "0px", "rgba(0,0,0,0)"]; const defaultShadowText = [',', "0px", "0px", "0px", "rgba(0,0,0,0)"]; while (shadowArray1.length < maxLength) { shadowArray1.push(...(property === "box-shadow" ? defaultShadowBox : defaultShadowText)); } while (shadowArray2.length < maxLength) { shadowArray2.push(...(property === "box-shadow" ? defaultShadowBox : defaultShadowText)); } return [[...shadowArray1], [...shadowArray2]]; } function parseAndUnifyFilters(from, to) { function filterToArray(filter) { const regex = /(\w+-?\w*)\(([^()]*\([^()]*\)[^()]*)\)|(\w+-?\w*)\(([^()]*)\)/g; const filters = []; let match; while ((match = regex.exec(filter)) !== null) { filters.push([match[1] || match[3], match[2] || match[4]]); } return filters; } const defaultFilters = { 'blur': '0px', 'brightness': '100%', 'contrast': '100%', 'grayscale': '0', 'hue-rotate': '0deg', 'invert': '0', 'opacity': '100%', 'saturate': '100%', 'sepia': '0', 'drop-shadow': '0px 0px 0px black', }; function unifyFilters(from, to) { const resFrom = []; const resTo = []; const parsedFrom = filterToArray(from); const parsedTo = filterToArray(to); const filterTypesFrom = parsedFrom.map((filter) => filter[0]); const filterTypesTo = parsedTo.map((filter) => filter[0]); let i = 0; while (i < parsedFrom.length && i < parsedTo.length && filterTypesFrom[i] === filterTypesTo[i]) { resFrom.push(parsedFrom[i]); resTo.push(parsedTo[i]); i++; } if (i === parsedFrom.length) { parsedTo.slice(i).forEach((object) => { resFrom.push([object[0], defaultFilters[object[0]]]); resTo.push(object); }); } else if (i === parsedTo.length) { parsedFrom.slice(i).forEach((object) => { resFrom.push(object); resTo.push([object[0], defaultFilters[object[0]]]); }); } else { parsedFrom.forEach((object) => { resFrom.push(object); resTo.push([object[0], defaultFilters[object[0]]]); }); parsedTo.forEach((object) => { resFrom.push([object[0], defaultFilters[object[0]]]); resTo.push(object); }); } return [resFrom, resTo]; } return unifyFilters(from, to); } function unifyValue(uc, propertyKey, from, to, fb) { var _a, _b; from = from !== null && from !== void 0 ? from : fb; to = (_a = to !== null && to !== void 0 ? to : from) !== null && _a !== void 0 ? _a : fb; propertyKey = animatry.camelToKebab(propertyKey); if (/^border-\w+-\w+-radius$/.test(propertyKey)) { const arrayFrom = disassambleBorderCornerRadius(from); const arrayTo = disassambleBorderCornerRadius(to); const arrayFallback = disassambleBorderCornerRadius(fb); const a = unifyValue(uc, 'elw', arrayFrom[0], arrayTo[0], arrayFallback[0]); const b = unifyValue(uc, 'elh', arrayFrom[1], arrayTo[1], arrayFallback[1]); return [[...a[0], ' ', ...b[0]], [...a[1], ' ', ...b[1]]]; } if (/^transform-origin$/.test(propertyKey)) { const arrayFrom = parsePositionProperty(propertyKey, from); const arrayTo = parsePositionProperty(propertyKey, to); const arrayFallback = parsePositionProperty(propertyKey, fb); const x = unifyValue(uc, 'elw', arrayFrom[0], arrayTo[0], arrayFallback[0]); const y = unifyValue(uc, 'elh', arrayFrom[1], arrayTo[1], arrayFallback[1]); const z = unifyValue(uc, '', arrayFrom[2], arrayTo[2], arrayFallback[2]); return [[...x[0], ' ', ...y[0], ' ', ...z[0]], [...x[1], ' ', ...y[1], ' ', ...z[1]]]; } if (/^background-position$/.test(propertyKey)) { const arrayFrom = parsePositionProperty(propertyKey, from); const arrayTo = parsePositionProperty(propertyKey, to); const arrayFallback = parsePositionProperty(propertyKey, fb); const x = unifyValue(uc, 'elw', arrayFrom[0], arrayTo[0], arrayFallback[0]); const y = unifyValue(uc, 'elh', arrayFrom[1], arrayTo[1], arrayFallback[1]); return [[...x[0], ' ', ...y[0]], [...x[1], ' ', ...y[1]]]; } if (/^(box|text)-shadow$/.test(propertyKey)) { const parsedShadows = parseAndUnifyShadows(propertyKey, from, to); return unifyAllValues(uc, parsedShadows[0], parsedShadows[1]); } if (/^filter$/.test(propertyKey)) { const res = parseAndUnifyFilters(from, to); let parsedFilters = [[], []]; res[0].forEach((obj, index) => { const start = obj[1]; const end = res[1][index][1]; const result = /^drop-shadow$/.test(obj[0]) ? unifyAllValues(uc, parseAndUnifyShadows('text-shadow', start, end)[0], parseAndUnifyShadows('text-shadow', start, end)[1]) : unifyValue(uc, /^invert$/.test(obj[0]) ? 'opacity' : /^hue-rotate$/.test(obj[0]) ? 'rotate' : '', start, end, start !== null && start !== void 0 ? start : end); parsedFilters[0].push(`${obj[0]}(`, ...stringifySignedNumber(result[0]), ') '); parsedFilters[1].push(`${obj[0]}(`, ...stringifySignedNumber(result[1]), ') '); }); return parsedFilters; } if (/^opacity$/.test(propertyKey)) { if (typeof from === 'string') from = from.replace(/(\d+)%/g, (_, p1) => (p1 / 100).toString()); if (typeof to === 'string') to = to.replace(/(\d+)%/g, (_, p1) => (p1 / 100).toString()); } if (/^(letter|word)-spacing$/.test(propertyKey)) { if (typeof from === 'string') from = from.replace('normal', '0px'); if (typeof to === 'string') to = to.replace('normal', '0px'); } if (validateColor(from) || validateColor(to)) { return [colorToRgba(from), colorToRgba(to)]; } if (isSignableNumber((_b = from) !== null && _b !== void 0 ? _b : to)) { return convertSignedNumbers(uc, propertyKey, toSignedNumber(from), toSignedNumber(to), toSignedNumber(fb)); } return [[from], [to]]; } function unifyProperties(uc, propertyKeys, from, to) { const res = [{}, {}, {}]; propertyKeys.forEach(propertyKey => { var _a, _b, _c; if (/^autoHide$/.test(propertyKey)) { if (propertyKeys.includes('visibility')) { animatry.warn(`remove 'visibility' in order to use 'autoHide'`); } } else if (!(propertyKey in document.documentElement.style)) { animatry.warn(`Unknown property '${propertyKey}'. Missing plugin?`); propertyKeys = propertyKeys.filter(k => k !== propertyKey); return; } else if (!CSS.supports(animatry.camelToKebab(propertyKey), (_a = from[propertyKey]) !== null && _a !== void 0 ? _a : to[propertyKey]) && !CSS.supports(animatry.camelToKebab(propertyKey), ((_b = from[propertyKey]) !== null && _b !== void 0 ? _b : to[propertyKey]) + 'px')) { animatry.warn(`Property ${propertyKey} has invalid value ${(_c = from[propertyKey]) !== null && _c !== void 0 ? _c : to[propertyKey]}`); return; } [from, to].forEach(object => { if (!object[propertyKey]) return; const splitableFunctions = [ [/^(border(Top|Right|Bottom|Left)?|outline)$/, parseBorderOrOutline], [/^(margin|padding)$/, parseMarginOrPadding], [/^borderRadius$/, parseBorderRadius] ]; for (const [regex, parseFunction] of splitableFunctions) { if (regex.test(propertyKey)) { const newProperties = parseFunction(propertyKey, object[propertyKey]); Object.assign(object, newProperties); propertyKeys = propertyKeys .filter(k => k !== propertyKey) .concat(Object.keys(newProperties).filter(k => !propertyKeys.includes(k))); delete object[propertyKey]; break; } } }); }); propertyKeys.forEach(propertyKey => { [res[0][propertyKey],] = unifyValue(uc, propertyKey, undefined, undefined, uc.css[propertyKey]); [res[1][propertyKey], res[2][propertyKey]] = unifyValue(uc, propertyKey, from[propertyKey], to[propertyKey], uc.css[propertyKey]); }); return res; } const RAD_TO_DEGREE = 180 / Math.PI; const vectorLength = ([x, y, z]) => Math.sqrt(x * x + y * y + z * z); const normalizeVector = ([x, y, z], length) => [x, y, z].map(s => s * 1 / length); const crossProduct = ([x1, y1, z1], [x2, y2, z2]) => [y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2]; const dotProduct = ([x1, y1, z1], [x2, y2, z2]) => x1 * x2 + y1 * y2 + z1 * z2; const linearCombination = ([x1, y1, z1], [x2, y2, z2], s1, s2) => [x1 * s1 + x2 * s2, y1 * s1 + y2 * s2, z1 * s1 + z2 * s2]; function decomposeMatrix(matrix) { const dM = new DOMMatrix(matrix); const ma = [ [dM.m11, dM.m12, dM.m13], [dM.m21, dM.m22, dM.m23], [dM.m31, dM.m32, dM.m33], ]; const scale = new Array(3); const skew = new Array(3); scale[0] = vectorLength(ma[0]); ma[0] = normalizeVector(ma[0], scale[0]); skew[0] = dotProduct(ma[0], ma[1]); ma[1] = linearCombination(ma[1], ma[0], 1.0, -skew[0]); scale[1] = vectorLength(ma[1]); ma[1] = normalizeVector(ma[1], scale[1]); skew[0] /= scale[1]; skew[1] = dotProduct(ma[0], ma[2]); ma[2] = linearCombination(ma[2], ma[0], 1.0, -skew[1]); skew[2] = dotProduct(ma[1], ma[2]); ma[2] = linearCombination(ma[2], ma[1], 1.0, -skew[2]); skew[0] = Math.atan(skew[0]); skew[1] = Math.atan(skew[1]); skew[2] = Math.atan(skew[2]); scale[2] = vectorLength(ma[2]); ma[2] = normalizeVector(ma[2], scale[2]); skew[1] /= scale[2]; skew[2] /= scale[2]; if (dotProduct(ma[0], crossProduct(ma[1], ma[2])) < 0) { for (let i = 0; i < 3; i++) { scale[i] *= -1; ma[i] = ma[i].map(val => -val); } } const quaternion = new Array(4); quaternion[0] = 0.5 * Math.sqrt(Math.max(1 + ma[0][0] - ma[1][1] - ma[2][2], 0)); quaternion[1] = 0.5 * Math.sqrt(Math.max(1 - ma[0][0] + ma[1][1] - ma[2][2], 0)); quaternion[2] = 0.5 * Math.sqrt(Math.max(1 - ma[0][0] - ma[1][1] + ma[2][2], 0)); quaternion[3] = 0.5 * Math.sqrt(Math.max(1 + ma[0][0] + ma[1][1] + ma[2][2], 0)); if (ma[2][1] > ma[1][2]) quaternion[0] = -quaternion[0]; if (ma[0][2] > ma[2][0]) quaternion[1] = -quaternion[1]; if (ma[1][0] > ma[0][1]) quaternion[2] = -quaternion[2]; const [qx, qy