js/Mobilizing/misc/Animation.js
import * as _Math from '../core/util/Math';
import Time from '../time/Time';
import Component from '../core/Component';
/**
* Fired when the animation starts
* @event start
* @param {Object} target The target object
*/
const EVT_START = 'start';
/**
* Fired each time the animation is updated
* @event update
* @param {Object} target The target object
*/
const EVT_UPDATE = 'update';
/**
* Fired when each time the animation is repeated once it reached the end if repeat is greater than 1
* @event restart
* @param {Object} target The target object
* @param {Number} direction The current direction
*/
const EVT_RESTART = 'restart';
/**
* Fired when the animation is stopped
* @event stop
* @param {Object} target The target object
*/
const EVT_STOP = 'stop';
/**
* Fired when the animation is resumed
* @event resume
* @param {Object} target The target object
*/
const EVT_RESUME = 'resume';
/**
* Fired when the animation reaches the end and no repetition is pending
* @event finish
* @param {Object} target The target object
*/
const EVT_FINISH = 'finish';
/**
* The Animation class provides a simple way to tween object properties
* @extends Component
* @example
* //TODO
*/
export default class Animation extends Component {
/**
* @param {Object} params The parameters object
* @param {Object} params.target The object whose propoerties are to be animated
* @param {Object} [params.from] An object indicating the start values of the properties to animate, defaults to the values of the target
* @param {Object} params.to An object indicating the finish values of the properties to animate
* @param {Number} params.duration The animation duration in milliseconds
* @param {Function} [params.easing=Animation.Easing.linear] An easing function to use
* @param {Number} [params.repeat=0] The number of times the animation should be repeated, set to Infinity to repeat indefinately
* @param {Boolean} [params.yoyo=false] If set to true and repeat is greater than 1, the animation will play in reverse once it reached the end
* @param {Number} [params.delay=0] The number of milliseconds to wait for before starting the animation
* @param {Time} [params.time] The Time instance to use for this Animation Component
* @param {Function} [params.onStart] A callback to be called when the animation starts
* @param {Function} [params.onUpdate] A callback to be called each time the animation is updated
* @param {Function} [params.onRestart] A callback to be called each time the animation is repeated once it reached the end if repeat is greater than 1
* @param {Function} [params.onStop] A callback to be called when the animation is stopped
* @param {Function} [params.onResume] A callback to be called when the animation is resumed
* @param {Function} [params.onFinish] A callback to be called when the animation reaches the end and no repetition is pending
*/
constructor({
target = undefined,
from = null,
to = null,
duration = null,
easing = Animation.Easing.linear,
repeat = 0,
yoyo = false,
delay = 0,
onStart = null,
onUpdate = null,
onRestart = null,
onStop = null,
onResume = null,
onFinish = null,
} = {}) {
super(...arguments);
this.target = target;
this.from = from;
this.to = to;
this.duration = duration;
this.easing = easing;
this.repeat = repeat;
this.yoyo = yoyo;
this.delay = delay;
this.onStart = onStart;
this.onUpdate = onUpdate;
this.onRestart = onRestart;
this.onStop = onStop;
this.onResume = onResume;
this.onFinish = onFinish;
this._time = new Time();
this._timesPlayed = 0;
this._direction = 1;
// bind custom callbacks to events
if (this.onStart) {
this.events.on(EVT_START, this.onStart);
}
if (this.onUpdate) {
this.events.on(EVT_UPDATE, this.onUpdate);
}
if (this.onRestart) {
this.events.on(EVT_RESTART, this.onRestart);
}
if (this.onStop) {
this.events.on(EVT_STOP, this.onStop);
}
if (this.onResume) {
this.events.on(EVT_RESUME, this.onResume);
}
if (this.onFinish) {
this.events.on(EVT_FINISH, this.onFinish);
}
}
/**
* Setup
*/
setup() {
const context = this.context;
context.addComponent(this._time);
this._time.setup();
this._time.on();
this._isPlaying = false;
}
/**
* Play the animation
*/
play() {
this._isPlaying = true;
this._time.reset();
if (!this.from) {
// fill in the start values from the target
this.from = {};
Object.keys(this.to).forEach((prop) => {
this.from[prop] = this.target[prop];
});
}
this.events.trigger(EVT_START, this.target);
}
/**
* Stop the animation
*/
stop() {
this._isPlaying = false;
this.events.trigger(EVT_STOP, this.target);
}
/**
* Resume the animation
*/
resume() {
}
/**
* Rewind the animation back to its starting values
*/
rewind() {
this.update(0);
}
/**
* Update the properties according to the elapsed time
*/
update(time) {
if (!this._isPlaying) {
return;
}
const t = (typeof time !== "undefined" ? time : (this._time.getAbsoluteDelta())) / this.duration;
if (t >= 1) {
Object.keys(this.from).forEach((prop) => {
this.target[prop] = this.to[prop];
});
if (this._timesPlayed++ < this.repeat) {
if (this.yoyo) {
this._direction *= -1;
}
this._time.reset();
this.events.trigger(EVT_RESTART, this.target, this._direction);
}
else {
this._isPlaying = false;
this.events.trigger(EVT_FINISH, this.target);
}
}
else {
for (const prop in this.from) {
if (this._direction < 1) {
this.target[prop] = _Math.map(this.easing(t), 0, 1, this.to[prop], this.from[prop]);
}
else {
this.target[prop] = _Math.map(this.easing(t), 0, 1, this.from[prop], this.to[prop]);
}
}
this.events.trigger(EVT_UPDATE, this.target);
}
}
/**
* Chain another animation once this one is finished
*/
chain(animation) {
this.events.on(EVT_FINISH, () => {
animation.play();
});
}
}
// credits: https://gist.github.com/gre/1650294
Animation.Easing = {
"linear": function (t) {
return t;
},
"easeInQuad": function (t) {
return Math.pow(t, 2);
},
"easeOutQuad": function (t) {
return 1 - Math.abs(Math.pow(t - 1, 2));
},
"easeInOutQuad": function (t) {
if (t < 0.5) {
return Animation.Easing.easeInQuad(t * 2) / 2;
}
return Animation.Easing.easeOutQuad(t * 2 - 1) / 2 + 0.5;
},
"easeInCubic": function (t) {
return Math.pow(t, 3);
},
"easeOutCubic": function (t) {
return 1 - Math.abs(Math.pow(t - 1, 3));
},
"easeInOutCubic": function (t) {
if (t < 0.5) {
return Animation.Easing.easeInCubic(t * 2) / 2;
}
return Animation.Easing.easeOutCubic(t * 2 - 1) / 2 + 0.5;
},
"easeInQuart": function (t) {
return Math.pow(t, 4);
},
"easeOutQuart": function (t) {
return 1 - Math.abs(Math.pow(t - 1, 4));
},
"easeInOutQuart": function (t) {
if (t < 0.5) {
return Animation.Easing.easeInQuart(t * 2) / 2;
}
return Animation.Easing.easeOutQuart(t * 2 - 1) / 2 + 0.5;
},
"easeInQuint": function (t) {
return Math.pow(t, 5);
},
"easeOutQuint": function (t) {
return 1 - Math.abs(Math.pow(t - 1, 5));
},
"easeInOutQuint": function (t) {
if (t < 0.5) {
return Animation.Easing.easeInQuint(t * 2) / 2;
}
return Animation.Easing.easeOutQuint(t * 2 - 1) / 2 + 0.5;
},
"easeInSin": function (t) {
return 1 + Math.sin(Math.PI / 2 * t - Math.PI / 2);
},
"easeOutSin": function (t) {
return Math.sin(Math.PI / 2 * t);
},
"easeInOutSin": function (t) {
return (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2;
},
"easeInElastic": function (t) {
return (0.04 - 0.04 / t) * Math.sin(25 * t) + 1;
},
"easeOutElastic": function (t) {
return 0.04 * t / (--t) * Math.sin(25 * t);
},
"easeInOutElastic": function (t) {
if ((t -= 0.5) < 0) {
return (0.01 + 0.01 / t) * Math.sin(50 * t);
}
return (0.02 - 0.01 / t) * Math.sin(50 * t) + 1;
},
"easeInBounce": function (t) {
return 1 - Animation.Easing.easeOutBounce(1 - t);
},
"easeOutBounce": function (t) {
if (t < (1 / 2.75)) {
return 7.5625 * t * t;
}
else if (t < (2 / 2.75)) {
return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
}
else if (t < (2.5 / 2.75)) {
return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
}
return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
},
"easeInOutBounce": function (t) {
if (t < 0.5) {
return Animation.Easing.easeInBounce(t * 2) * 0.5;
}
return Animation.Easing.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
}
};