Animatable.js 6.5 KB
var Animator = require("../animation/Animator");

var log = require("../core/log");

var _util = require("../core/util");

var isString = _util.isString;
var isFunction = _util.isFunction;
var isObject = _util.isObject;
var isArrayLike = _util.isArrayLike;
var indexOf = _util.indexOf;

/**
 * @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;
  }
};
var _default = Animatable;
module.exports = _default;