travel/admin/node_modules/zrender/lib/animation/Animator.js

644 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = {
/**
* 设置动画关键帧
* @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);
}
},
/**
* 开始执行动画
* @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;
},
/**
* 停止动画
* @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;
},
/**
* 设置动画延迟开始的时间
* @param {number} time 单位ms
* @return {module:zrender/animation/Animator}
*/
delay: function (time) {
this._delay = time;
return this;
},
/**
* 添加动画结束的回调
* @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;