| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 | var Clip = require("./Clip");var color = require("../tool/color");var _util = require("../core/util");var isArrayLike = _util.isArrayLike;/** * @module echarts/animation/Animator */var arraySlice = Array.prototype.slice;function defaultGetter(target, key) {  return target[key];}function defaultSetter(target, key, value) {  target[key] = value;}/** * @param  {number} p0 * @param  {number} p1 * @param  {number} percent * @return {number} */function interpolateNumber(p0, p1, percent) {  return (p1 - p0) * percent + p0;}/** * @param  {string} p0 * @param  {string} p1 * @param  {number} percent * @return {string} */function interpolateString(p0, p1, percent) {  return percent > 0.5 ? p1 : p0;}/** * @param  {Array} p0 * @param  {Array} p1 * @param  {number} percent * @param  {Array} out * @param  {number} arrDim */function interpolateArray(p0, p1, percent, out, arrDim) {  var len = p0.length;  if (arrDim === 1) {    for (var i = 0; i < len; i++) {      out[i] = interpolateNumber(p0[i], p1[i], percent);    }  } else {    var len2 = len && p0[0].length;    for (var i = 0; i < len; i++) {      for (var j = 0; j < len2; j++) {        out[i][j] = interpolateNumber(p0[i][j], p1[i][j], percent);      }    }  }} // arr0 is source array, arr1 is target array.// Do some preprocess to avoid error happened when interpolating from arr0 to arr1function fillArr(arr0, arr1, arrDim) {  var arr0Len = arr0.length;  var arr1Len = arr1.length;  if (arr0Len !== arr1Len) {    // FIXME Not work for TypedArray    var isPreviousLarger = arr0Len > arr1Len;    if (isPreviousLarger) {      // Cut the previous      arr0.length = arr1Len;    } else {      // Fill the previous      for (var i = arr0Len; i < arr1Len; i++) {        arr0.push(arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i]));      }    }  } // Handling NaN value  var len2 = arr0[0] && arr0[0].length;  for (var i = 0; i < arr0.length; i++) {    if (arrDim === 1) {      if (isNaN(arr0[i])) {        arr0[i] = arr1[i];      }    } else {      for (var j = 0; j < len2; j++) {        if (isNaN(arr0[i][j])) {          arr0[i][j] = arr1[i][j];        }      }    }  }}/** * @param  {Array} arr0 * @param  {Array} arr1 * @param  {number} arrDim * @return {boolean} */function isArraySame(arr0, arr1, arrDim) {  if (arr0 === arr1) {    return true;  }  var len = arr0.length;  if (len !== arr1.length) {    return false;  }  if (arrDim === 1) {    for (var i = 0; i < len; i++) {      if (arr0[i] !== arr1[i]) {        return false;      }    }  } else {    var len2 = arr0[0].length;    for (var i = 0; i < len; i++) {      for (var j = 0; j < len2; j++) {        if (arr0[i][j] !== arr1[i][j]) {          return false;        }      }    }  }  return true;}/** * Catmull Rom interpolate array * @param  {Array} p0 * @param  {Array} p1 * @param  {Array} p2 * @param  {Array} p3 * @param  {number} t * @param  {number} t2 * @param  {number} t3 * @param  {Array} out * @param  {number} arrDim */function catmullRomInterpolateArray(p0, p1, p2, p3, t, t2, t3, out, arrDim) {  var len = p0.length;  if (arrDim === 1) {    for (var i = 0; i < len; i++) {      out[i] = catmullRomInterpolate(p0[i], p1[i], p2[i], p3[i], t, t2, t3);    }  } else {    var len2 = p0[0].length;    for (var i = 0; i < len; i++) {      for (var j = 0; j < len2; j++) {        out[i][j] = catmullRomInterpolate(p0[i][j], p1[i][j], p2[i][j], p3[i][j], t, t2, t3);      }    }  }}/** * Catmull Rom interpolate number * @param  {number} p0 * @param  {number} p1 * @param  {number} p2 * @param  {number} p3 * @param  {number} t * @param  {number} t2 * @param  {number} t3 * @return {number} */function catmullRomInterpolate(p0, p1, p2, p3, t, t2, t3) {  var v0 = (p2 - p0) * 0.5;  var v1 = (p3 - p1) * 0.5;  return (2 * (p1 - p2) + v0 + v1) * t3 + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + v0 * t + p1;}function cloneValue(value) {  if (isArrayLike(value)) {    var len = value.length;    if (isArrayLike(value[0])) {      var ret = [];      for (var i = 0; i < len; i++) {        ret.push(arraySlice.call(value[i]));      }      return ret;    }    return arraySlice.call(value);  }  return value;}function rgba2String(rgba) {  rgba[0] = Math.floor(rgba[0]);  rgba[1] = Math.floor(rgba[1]);  rgba[2] = Math.floor(rgba[2]);  return 'rgba(' + rgba.join(',') + ')';}function getArrayDim(keyframes) {  var lastValue = keyframes[keyframes.length - 1].value;  return isArrayLike(lastValue && lastValue[0]) ? 2 : 1;}function createTrackClip(animator, easing, oneTrackDone, keyframes, propName, forceAnimate) {  var getter = animator._getter;  var setter = animator._setter;  var useSpline = easing === 'spline';  var trackLen = keyframes.length;  if (!trackLen) {    return;  } // Guess data type  var firstVal = keyframes[0].value;  var isValueArray = isArrayLike(firstVal);  var isValueColor = false;  var isValueString = false; // For vertices morphing  var arrDim = isValueArray ? getArrayDim(keyframes) : 0;  var trackMaxTime; // Sort keyframe as ascending  keyframes.sort(function (a, b) {    return a.time - b.time;  });  trackMaxTime = keyframes[trackLen - 1].time; // Percents of each keyframe  var kfPercents = []; // Value of each keyframe  var kfValues = [];  var prevValue = keyframes[0].value;  var isAllValueEqual = true;  for (var i = 0; i < trackLen; i++) {    kfPercents.push(keyframes[i].time / trackMaxTime); // Assume value is a color when it is a string    var value = keyframes[i].value; // Check if value is equal, deep check if value is array    if (!(isValueArray && isArraySame(value, prevValue, arrDim) || !isValueArray && value === prevValue)) {      isAllValueEqual = false;    }    prevValue = value; // Try converting a string to a color array    if (typeof value === 'string') {      var colorArray = color.parse(value);      if (colorArray) {        value = colorArray;        isValueColor = true;      } else {        isValueString = true;      }    }    kfValues.push(value);  }  if (!forceAnimate && isAllValueEqual) {    return;  }  var lastValue = kfValues[trackLen - 1]; // Polyfill array and NaN value  for (var i = 0; i < trackLen - 1; i++) {    if (isValueArray) {      fillArr(kfValues[i], lastValue, arrDim);    } else {      if (isNaN(kfValues[i]) && !isNaN(lastValue) && !isValueString && !isValueColor) {        kfValues[i] = lastValue;      }    }  }  isValueArray && fillArr(getter(animator._target, propName), lastValue, arrDim); // Cache the key of last frame to speed up when  // animation playback is sequency  var lastFrame = 0;  var lastFramePercent = 0;  var start;  var w;  var p0;  var p1;  var p2;  var p3;  if (isValueColor) {    var rgba = [0, 0, 0, 0];  }  var onframe = function (target, percent) {    // Find the range keyframes    // kf1-----kf2---------current--------kf3    // find kf2 and kf3 and do interpolation    var frame; // In the easing function like elasticOut, percent may less than 0    if (percent < 0) {      frame = 0;    } else if (percent < lastFramePercent) {      // Start from next key      // PENDING start from lastFrame ?      start = Math.min(lastFrame + 1, trackLen - 1);      for (frame = start; frame >= 0; frame--) {        if (kfPercents[frame] <= percent) {          break;        }      } // PENDING really need to do this ?      frame = Math.min(frame, trackLen - 2);    } else {      for (frame = lastFrame; frame < trackLen; frame++) {        if (kfPercents[frame] > percent) {          break;        }      }      frame = Math.min(frame - 1, trackLen - 2);    }    lastFrame = frame;    lastFramePercent = percent;    var range = kfPercents[frame + 1] - kfPercents[frame];    if (range === 0) {      return;    } else {      w = (percent - kfPercents[frame]) / range;    }    if (useSpline) {      p1 = kfValues[frame];      p0 = kfValues[frame === 0 ? frame : frame - 1];      p2 = kfValues[frame > trackLen - 2 ? trackLen - 1 : frame + 1];      p3 = kfValues[frame > trackLen - 3 ? trackLen - 1 : frame + 2];      if (isValueArray) {        catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, getter(target, propName), arrDim);      } else {        var value;        if (isValueColor) {          value = catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, rgba, 1);          value = rgba2String(rgba);        } else if (isValueString) {          // String is step(0.5)          return interpolateString(p1, p2, w);        } else {          value = catmullRomInterpolate(p0, p1, p2, p3, w, w * w, w * w * w);        }        setter(target, propName, value);      }    } else {      if (isValueArray) {        interpolateArray(kfValues[frame], kfValues[frame + 1], w, getter(target, propName), arrDim);      } else {        var value;        if (isValueColor) {          interpolateArray(kfValues[frame], kfValues[frame + 1], w, rgba, 1);          value = rgba2String(rgba);        } else if (isValueString) {          // String is step(0.5)          return interpolateString(kfValues[frame], kfValues[frame + 1], w);        } else {          value = interpolateNumber(kfValues[frame], kfValues[frame + 1], w);        }        setter(target, propName, value);      }    }  };  var clip = new Clip({    target: animator._target,    life: trackMaxTime,    loop: animator._loop,    delay: animator._delay,    onframe: onframe,    ondestroy: oneTrackDone  });  if (easing && easing !== 'spline') {    clip.easing = easing;  }  return clip;}/** * @alias module:zrender/animation/Animator * @constructor * @param {Object} target * @param {boolean} loop * @param {Function} getter * @param {Function} setter */var Animator = function (target, loop, getter, setter) {  this._tracks = {};  this._target = target;  this._loop = loop || false;  this._getter = getter || defaultGetter;  this._setter = setter || defaultSetter;  this._clipCount = 0;  this._delay = 0;  this._doneList = [];  this._onframeList = [];  this._clipList = [];};Animator.prototype = {  /**   * Set Animation keyframe   * @param  {number} time 关键帧时间,单位是ms   * @param  {Object} props 关键帧的属性值,key-value表示   * @return {module:zrender/animation/Animator}   */  when: function (time  /* ms */  , props) {    var tracks = this._tracks;    for (var propName in props) {      if (!props.hasOwnProperty(propName)) {        continue;      }      if (!tracks[propName]) {        tracks[propName] = []; // Invalid value        var value = this._getter(this._target, propName);        if (value == null) {          // zrLog('Invalid property ' + propName);          continue;        } // If time is 0        //  Then props is given initialize value        // Else        //  Initialize value from current prop value        if (time !== 0) {          tracks[propName].push({            time: 0,            value: cloneValue(value)          });        }      }      tracks[propName].push({        time: time,        value: props[propName]      });    }    return this;  },  /**   * 添加动画每一帧的回调函数   * @param  {Function} callback   * @return {module:zrender/animation/Animator}   */  during: function (callback) {    this._onframeList.push(callback);    return this;  },  pause: function () {    for (var i = 0; i < this._clipList.length; i++) {      this._clipList[i].pause();    }    this._paused = true;  },  resume: function () {    for (var i = 0; i < this._clipList.length; i++) {      this._clipList[i].resume();    }    this._paused = false;  },  isPaused: function () {    return !!this._paused;  },  _doneCallback: function () {    // Clear all tracks    this._tracks = {}; // Clear all clips    this._clipList.length = 0;    var doneList = this._doneList;    var len = doneList.length;    for (var i = 0; i < len; i++) {      doneList[i].call(this);    }  },  /**   * Start the animation   * @param  {string|Function} [easing]   *         动画缓动函数,详见{@link module:zrender/animation/easing}   * @param  {boolean} forceAnimate   * @return {module:zrender/animation/Animator}   */  start: function (easing, forceAnimate) {    var self = this;    var clipCount = 0;    var oneTrackDone = function () {      clipCount--;      if (!clipCount) {        self._doneCallback();      }    };    var lastClip;    for (var propName in this._tracks) {      if (!this._tracks.hasOwnProperty(propName)) {        continue;      }      var clip = createTrackClip(this, easing, oneTrackDone, this._tracks[propName], propName, forceAnimate);      if (clip) {        this._clipList.push(clip);        clipCount++; // If start after added to animation        if (this.animation) {          this.animation.addClip(clip);        }        lastClip = clip;      }    } // Add during callback on the last clip    if (lastClip) {      var oldOnFrame = lastClip.onframe;      lastClip.onframe = function (target, percent) {        oldOnFrame(target, percent);        for (var i = 0; i < self._onframeList.length; i++) {          self._onframeList[i](target, percent);        }      };    } // This optimization will help the case that in the upper application    // the view may be refreshed frequently, where animation will be    // called repeatly but nothing changed.    if (!clipCount) {      this._doneCallback();    }    return this;  },  /**   * Stop animation   * @param {boolean} forwardToLast If move to last frame before stop   */  stop: function (forwardToLast) {    var clipList = this._clipList;    var animation = this.animation;    for (var i = 0; i < clipList.length; i++) {      var clip = clipList[i];      if (forwardToLast) {        // Move to last frame before stop        clip.onframe(this._target, 1);      }      animation && animation.removeClip(clip);    }    clipList.length = 0;  },  /**   * Set when animation delay starts   * @param  {number} time 单位ms   * @return {module:zrender/animation/Animator}   */  delay: function (time) {    this._delay = time;    return this;  },  /**   * Add callback for animation end   * @param  {Function} cb   * @return {module:zrender/animation/Animator}   */  done: function (cb) {    if (cb) {      this._doneList.push(cb);    }    return this;  },  /**   * @return {Array.<module:zrender/animation/Clip>}   */  getClips: function () {    return this._clipList;  }};var _default = Animator;module.exports = _default;
 |