| import Animator from '../animation/Animator'; |
| import log from '../core/log'; |
| import { isString, isFunction, isObject, isArrayLike, indexOf } from '../core/util'; |
| /** |
| * @alias modue:zrender/mixin/Animatable |
| * @constructor |
| */ |
| |
| var Animatable = function () { |
| /** |
| * @type {Array.<module:zrender/animation/Animator>} |
| * @readOnly |
| */ |
| this.animators = []; |
| }; |
| |
| Animatable.prototype = { |
| constructor: Animatable, |
| |
| /** |
| * 动画 |
| * |
| * @param {string} path The path to fetch value from object, like 'a.b.c'. |
| * @param {boolean} [loop] Whether to loop animation. |
| * @return {module:zrender/animation/Animator} |
| * @example: |
| * el.animate('style', false) |
| * .when(1000, {x: 10} ) |
| * .done(function(){ // Animation done }) |
| * .start() |
| */ |
| animate: function (path, loop) { |
| var target; |
| var animatingShape = false; |
| var el = this; |
| var zr = this.__zr; |
| |
| if (path) { |
| var pathSplitted = path.split('.'); |
| var prop = el; // If animating shape |
| |
| animatingShape = pathSplitted[0] === 'shape'; |
| |
| for (var i = 0, l = pathSplitted.length; i < l; i++) { |
| if (!prop) { |
| continue; |
| } |
| |
| prop = prop[pathSplitted[i]]; |
| } |
| |
| if (prop) { |
| target = prop; |
| } |
| } else { |
| target = el; |
| } |
| |
| if (!target) { |
| log('Property "' + path + '" is not existed in element ' + el.id); |
| return; |
| } |
| |
| var animators = el.animators; |
| var animator = new Animator(target, loop); |
| animator.during(function (target) { |
| el.dirty(animatingShape); |
| }).done(function () { |
| // FIXME Animator will not be removed if use `Animator#stop` to stop animation |
| animators.splice(indexOf(animators, animator), 1); |
| }); |
| animators.push(animator); // If animate after added to the zrender |
| |
| if (zr) { |
| zr.animation.addAnimator(animator); |
| } |
| |
| return animator; |
| }, |
| |
| /** |
| * 停止动画 |
| * @param {boolean} forwardToLast If move to last frame before stop |
| */ |
| stopAnimation: function (forwardToLast) { |
| var animators = this.animators; |
| var len = animators.length; |
| |
| for (var i = 0; i < len; i++) { |
| animators[i].stop(forwardToLast); |
| } |
| |
| animators.length = 0; |
| return this; |
| }, |
| |
| /** |
| * Caution: this method will stop previous animation. |
| * So do not use this method to one element twice before |
| * animation starts, unless you know what you are doing. |
| * @param {Object} target |
| * @param {number} [time=500] Time in ms |
| * @param {string} [easing='linear'] |
| * @param {number} [delay=0] |
| * @param {Function} [callback] |
| * @param {Function} [forceAnimate] Prevent stop animation and callback |
| * immediently when target values are the same as current values. |
| * |
| * @example |
| * // Animate position |
| * el.animateTo({ |
| * position: [10, 10] |
| * }, function () { // done }) |
| * |
| * // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut easing |
| * el.animateTo({ |
| * shape: { |
| * width: 500 |
| * }, |
| * style: { |
| * fill: 'red' |
| * } |
| * position: [10, 10] |
| * }, 100, 100, 'cubicOut', function () { // done }) |
| */ |
| // TODO Return animation key |
| animateTo: function (target, time, delay, easing, callback, forceAnimate) { |
| // animateTo(target, time, easing, callback); |
| if (isString(delay)) { |
| callback = easing; |
| easing = delay; |
| delay = 0; |
| } // animateTo(target, time, delay, callback); |
| else if (isFunction(easing)) { |
| callback = easing; |
| easing = 'linear'; |
| delay = 0; |
| } // animateTo(target, time, callback); |
| else if (isFunction(delay)) { |
| callback = delay; |
| delay = 0; |
| } // animateTo(target, callback) |
| else if (isFunction(time)) { |
| callback = time; |
| time = 500; |
| } // animateTo(target) |
| else if (!time) { |
| time = 500; |
| } // Stop all previous animations |
| |
| |
| this.stopAnimation(); |
| |
| this._animateToShallow('', this, target, time, delay); // Animators may be removed immediately after start |
| // if there is nothing to animate |
| |
| |
| var animators = this.animators.slice(); |
| var count = animators.length; |
| |
| function done() { |
| count--; |
| |
| if (!count) { |
| callback && callback(); |
| } |
| } // No animators. This should be checked before animators[i].start(), |
| // because 'done' may be executed immediately if no need to animate. |
| |
| |
| if (!count) { |
| callback && callback(); |
| } // Start after all animators created |
| // Incase any animator is done immediately when all animation properties are not changed |
| |
| |
| for (var i = 0; i < animators.length; i++) { |
| animators[i].done(done).start(easing, forceAnimate); |
| } |
| }, |
| |
| /** |
| * @private |
| * @param {string} path='' |
| * @param {Object} source=this |
| * @param {Object} target |
| * @param {number} [time=500] |
| * @param {number} [delay=0] |
| * |
| * @example |
| * // Animate position |
| * el._animateToShallow({ |
| * position: [10, 10] |
| * }) |
| * |
| * // Animate shape, style and position in 100ms, delayed 100ms |
| * el._animateToShallow({ |
| * shape: { |
| * width: 500 |
| * }, |
| * style: { |
| * fill: 'red' |
| * } |
| * position: [10, 10] |
| * }, 100, 100) |
| */ |
| _animateToShallow: function (path, source, target, time, delay) { |
| var objShallow = {}; |
| var propertyCount = 0; |
| |
| for (var name in target) { |
| if (!target.hasOwnProperty(name)) { |
| continue; |
| } |
| |
| if (source[name] != null) { |
| if (isObject(target[name]) && !isArrayLike(target[name])) { |
| this._animateToShallow(path ? path + '.' + name : name, source[name], target[name], time, delay); |
| } else { |
| objShallow[name] = target[name]; |
| propertyCount++; |
| } |
| } else if (target[name] != null) { |
| // Attr directly if not has property |
| // FIXME, if some property not needed for element ? |
| if (!path) { |
| this.attr(name, target[name]); |
| } else { |
| // Shape or style |
| var props = {}; |
| props[path] = {}; |
| props[path][name] = target[name]; |
| this.attr(props); |
| } |
| } |
| } |
| |
| if (propertyCount > 0) { |
| this.animate(path, false).when(time == null ? 500 : time, objShallow).delay(delay || 0); |
| } |
| |
| return this; |
| } |
| }; |
| export default Animatable; |