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 arr1
- function 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;
|