import Easing from './easing'; function noop () {} /** * @constructor * @alias clay.animation.Clip * @param {Object} [opts] * @param {Object} [opts.target] * @param {number} [opts.life] * @param {number} [opts.delay] * @param {number} [opts.gap] * @param {number} [opts.playbackRate] * @param {boolean|number} [opts.loop] If loop is a number, it indicate the loop count of animation * @param {string|Function} [opts.easing] * @param {Function} [opts.onframe] * @param {Function} [opts.onfinish] * @param {Function} [opts.onrestart] */ var Clip = function (opts) { opts = opts || {}; /** * @type {string} */ this.name = opts.name || ''; /** * @type {Object} */ this.target = opts.target; /** * @type {number} */ this.life = opts.life || 1000; /** * @type {number} */ this.delay = opts.delay || 0; /** * @type {number} */ this.gap = opts.gap || 0; /** * @type {number} */ this.playbackRate = opts.playbackRate || 1; this._initialized = false; this._elapsedTime = 0; this._loop = opts.loop == null ? false : opts.loop; this.setLoop(this._loop); if (opts.easing != null) { this.setEasing(opts.easing); } /** * @type {Function} */ this.onframe = opts.onframe || noop; /** * @type {Function} */ this.onfinish = opts.onfinish || noop; /** * @type {Function} */ this.onrestart = opts.onrestart || noop; this._paused = false; }; Clip.prototype = { gap: 0, life: 0, delay: 0, /** * @param {number|boolean} loop */ setLoop: function (loop) { this._loop = loop; if (loop) { if (typeof loop === 'number') { this._loopRemained = loop; } else { this._loopRemained = Infinity; } } }, /** * @param {string|Function} easing */ setEasing: function (easing) { if (typeof(easing) === 'string') { easing = Easing[easing]; } this.easing = easing; }, /** * @param {number} time * @return {string} */ step: function (time, deltaTime, silent) { if (!this._initialized) { this._startTime = time + this.delay; this._initialized = true; } if (this._currentTime != null) { deltaTime = time - this._currentTime; } this._currentTime = time; if (this._paused) { return 'paused'; } if (time < this._startTime) { return; } // PENDIGN Sync ? this._elapse(time, deltaTime); var percent = Math.min(this._elapsedTime / this.life, 1); if (percent < 0) { return; } var schedule; if (this.easing) { schedule = this.easing(percent); } else { schedule = percent; } if (!silent) { this.fire('frame', schedule); } if (percent === 1) { if (this._loop && this._loopRemained > 0) { this._restartInLoop(time); this._loopRemained--; return 'restart'; } else { // Mark this clip to be deleted // In the animation.update this._needsRemove = true; return 'finish'; } } else { return null; } }, /** * @param {number} time * @return {string} */ setTime: function (time) { return this.step(time + this._startTime); }, restart: function (time) { // If user leave the page for a while, when he gets back // All clips may be expired and all start from the beginning value(position) // It is clearly wrong, so we use remainder to add a offset var remainder = 0; // Remainder ignored if restart is invoked manually if (time) { this._elapse(time); remainder = this._elapsedTime % this.life; } time = time || Date.now(); this._startTime = time - remainder + this.delay; this._elapsedTime = 0; this._needsRemove = false; this._paused = false; }, getElapsedTime: function () { return this._elapsedTime; }, _restartInLoop: function (time) { this._startTime = time + this.gap; this._elapsedTime = 0; }, _elapse: function (time, deltaTime) { this._elapsedTime += deltaTime * this.playbackRate; }, fire: function (eventType, arg) { var eventName = 'on' + eventType; if (this[eventName]) { this[eventName](this.target, arg); } }, clone: function () { var clip = new this.constructor(); clip.name = this.name; clip._loop = this._loop; clip._loopRemained = this._loopRemained; clip.life = this.life; clip.gap = this.gap; clip.delay = this.delay; return clip; }, /** * Pause the clip. */ pause: function () { this._paused = true; }, /** * Resume the clip. */ resume: function () { this._paused = false; } }; Clip.prototype.constructor = Clip; export default Clip;