animatry
Version:
Javascript animation library.
1,226 lines (1,219 loc) • 82.6 kB
JavaScript
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