@mapgis/webclient-es6-mapboxgl
Version:
681 lines (602 loc) • 22 kB
JavaScript
import { Common } from '@mapgis/webclient-es6-service';
const { Zondy, extend } = Common;
import {Util} from './Util';
import {Eventful} from './Eventful';
import {Clip} from './Clip';
import {SUtil} from './SUtil';
/**
* @private
* @class Zondy.LevelRenderer.Animation
* @classdesc 动画主类, 调度和管理所有动画控制器
* @extends {Zondy.LevelRenderer.Eventful}
*/
class Animation extends Eventful {
/**
* @function Zondy.LevelRenderer.Animation.prototype.constructor
* @description 构造函数。
* @param {Object} options - 动画参数。
* @param {Object} options.onframe - onframe。
* @param {Object} options.stage - stage。
* (start code)
* var animation = new Zondy.LevelRenderer.Animation();
* var obj = {
* x: 100,
* y: 100
* };
* animation.animate(node.position)
* .when(1000, {
* x: 500,
* y: 500
* })
* .when(2000, {
* x: 100,
* y: 100
* })
* .start('spline');
* (end)
*/
constructor(options) {
super(options);
options = options || {};
/**
* @member {Object} Zondy.LevelRenderer.Animation.prototype.stage
* @description stage。
*/
this.stage = {};
/**
* @member {Object} Zondy.LevelRenderer.Animation.prototype.onframe
* @description onframe。
*/
this.onframe = function () {
};
/**
* @member {Array} Zondy.LevelRenderer.Animation.prototype._clips
* @description _clips。
*/
this._clips = [];
/**
* @member {boolean} Zondy.LevelRenderer.Animation.prototype._running
* @description _running。
*/
this._running = false;
/**
* @member {number} Zondy.LevelRenderer.Animation.prototype._time
* @description _time。
*/
this._time = 0;
extend(this, options);
this.CLASS_NAME = "Zondy.LevelRenderer.Animation";
}
/**
* @function Zondy.LevelRenderer.Animation.prototype.add
* @description 添加动画片段。
* @param {Zondy.LevelRenderer.Animation.Clip} clip - 动画片段。
*/
add(clip) {
this._clips.push(clip);
}
/**
* @function Zondy.LevelRenderer.Animation.prototype.remove
* @description 删除动画片段。
* @param {Zondy.LevelRenderer.Animation.Clip} clip - 动画片段。
*/
remove(clip) {
var idx = new Util().indexOf(this._clips, clip);
if (idx >= 0) {
this._clips.splice(idx, 1);
}
}
/**
* @function Zondy.LevelRenderer.Animation.prototype.update
* @description 更新动画片段。
*/
_update() {
var time = new Date().getTime();
var delta = time - this._time;
var clips = this._clips;
var len = clips.length;
var deferredEvents = [];
var deferredClips = [];
for (let i = 0; i < len; i++) {
var clip = clips[i];
var e = clip.step(time);
// Throw out the events need to be called after
// stage.update, like destroy
if (e) {
deferredEvents.push(e);
deferredClips.push(clip);
}
}
if (this.stage.update) {
this.stage.update();
}
// Remove the finished clip
for (let i = 0; i < len;) {
if (clips[i]._needsRemove) {
clips[i] = clips[len - 1];
clips.pop();
len--;
} else {
i++;
}
}
len = deferredEvents.length;
for (let i = 0; i < len; i++) {
deferredClips[i].fire(deferredEvents[i]);
}
this._time = time;
this.onframe(delta);
this.dispatch('frame', delta);
}
/**
* @function Zondy.LevelRenderer.Animation.prototype.start
* @description 开始运行动画。
*/
start() {
var requestAnimationFrame = window.requestAnimationFrame
|| window.msRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| function (func) {
setTimeout(func, 16);
};
var self = this;
this._running = true;
function step() {
if (self._running) {
self._update();
requestAnimationFrame(step);
}
}
this._time = new Date().getTime();
requestAnimationFrame(step);
}
/**
* @function Zondy.LevelRenderer.Animation.prototype.stop
* @description 停止运行动画。
*/
stop() {
this._running = false;
}
/**
* @function Zondy.LevelRenderer.Animation.prototype.clear
* @description 清除所有动画片段。
*/
clear() {
this._clips = [];
}
/**
* @function Zondy.LevelRenderer.Animation.prototype.animate
* @description 对一个目标创建一个animator对象,可以指定目标中的属性使用动画。
* @param {Object} target - 目标对象。
* @param {Object} options - 动画参数选项。
* @param {boolean} [options.loop=false] - 是否循环播放动画。
* @param {function} [options.getter] - 如果指定getter函数,会通过getter函数取属性值。
* @param {function} [options.setter] - 如果指定setter函数,会通过setter函数设置属性值。
* @returns {Zondy.LevelRenderer.Animation.Animator} Animator。
*/
animate(target, options) {
options = options || {};
var deferred = new Animator(
target,
options.loop,
options.getter,
options.setter
);
deferred.animation = this;
return deferred;
}
static _interpolateNumber(p0, p1, percent) {
return (p1 - p0) * percent + p0;
}
static _interpolateArray(p0, p1, percent, out, arrDim) {
var len = p0.length;
if (arrDim === 1) {
for (let i = 0; i < len; i++) {
out[i] = Animation._interpolateNumber(p0[i], p1[i], percent);
}
} else {
var len2 = p0[0].length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len2; j++) {
out[i][j] = Animation._interpolateNumber(
p0[i][j], p1[i][j], percent
);
}
}
}
}
static _isArrayLike(data) {
switch (typeof data) {
case 'undefined':
case 'string':
return false;
}
return typeof data.length !== 'undefined';
}
static _catmullRomInterpolateArray(p0, p1, p2, p3, t, t2, t3, out, arrDim) {
var len = p0.length;
if (arrDim === 1) {
for (let i = 0; i < len; i++) {
out[i] = Animation._catmullRomInterpolate(
p0[i], p1[i], p2[i], p3[i], t, t2, t3
);
}
} else {
var len2 = p0[0].length;
for (let i = 0; i < len; i++) {
for (var j = 0; j < len2; j++) {
out[i][j] = Animation._catmullRomInterpolate(
p0[i][j], p1[i][j], p2[i][j], p3[i][j],
t, t2, t3
);
}
}
}
}
static _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;
}
static _cloneValue(value) {
var arraySlice = Array.prototype.slice;
if (Animation._isArrayLike(value)) {
var len = value.length;
if (Animation._isArrayLike(value[0])) {
var ret = [];
for (var i = 0; i < len; i++) {
ret.push(arraySlice.call(value[i]));
}
return ret;
} else {
return arraySlice.call(value);
}
} else {
return value;
}
}
static rgba2String(rgba) {
rgba[0] = Math.floor(rgba[0]);
rgba[1] = Math.floor(rgba[1]);
rgba[2] = Math.floor(rgba[2]);
return 'rgba(' + rgba.join(',') + ')';
}
}
/**
* @class Zondy.LevelRenderer.Animation.Animator
*/
class Animator {
/**
* @function Zondy.LevelRenderer.Animation.Animator.prototype.animate
* @description 构造函数
* @param {Object} target - 目标对象。
* @param {Object} options - 动画参数选项。
* @param {boolean} [loop=false] - 是否循环播放动画。
* @param {function} [getter] - 如果指定getter函数,会通过getter函数取属性值。
* @param {function} [setter] - 如果指定setter函数,会通过setter函数设置属性值。
*/
constructor(target, loop, getter, setter) {
/**
* @member {Object} Zondy.LevelRenderer.Animation.Animator.prototype._tracks
* @description _tracks。
*/
this._tracks = {};
/**
* @member {Object} Zondy.LevelRenderer.Animation.Animator.prototype._target
* @description _target。
*/
this._target = target;
/**
* @member {boolean} Zondy.LevelRenderer.Animation.Animator.prototype._loop
* @description _loop。
*/
this._loop = loop || false;
/**
* @member {function} Zondy.LevelRenderer.Animation.Animator.prototype._getter
* @description _getter。
*/
this._getter = getter || _defaultGetter;
/**
* @member {function} Zondy.LevelRenderer.Animation.Animator.prototype._setter
* @description _setter。
*/
this._setter = setter || _defaultSetter;
/**
* @member {number} Zondy.LevelRenderer.Animation.Animator.prototype._clipCount
* @description _clipCount。
*/
this._clipCount = 0;
/**
* @member {number} Zondy.LevelRenderer.Animation.Animator.prototype._delay
* @description _delay。
*/
this._delay = 0;
/**
* @member {Array} Zondy.LevelRenderer.Animation.Animator.prototype._doneList
* @description _doneList。
*/
this._doneList = [];
/**
* @member {Array} Zondy.LevelRenderer.Animation.Animator.prototype._onframeList
* @description _onframeList。
*/
this._onframeList = [];
/**
* @member {Array} Zondy.LevelRenderer.Animation.Animator.prototype._clipList
* @description _clipList。
*/
this._clipList = [];
this.CLASS_NAME = "Zondy.LevelRenderer.Animation.Animator";
//Function
function _defaultGetter(target, key) {
return target[key];
}
function _defaultSetter(target, key, value) {
target[key] = value;
}
}
/**
* @function Zondy.LevelRenderer.Animation.Animator.prototype.when
* @description 设置动画关键帧
* @param {number} time - 关键帧时间,单位是ms
* @param {Object} props - 关键帧的属性值,key-value表示
* @returns {Zondy.LevelRenderer.Animation.Animator} Animator
*/
when(time /* ms */, props) {
for (var propName in props) {
if (!this._tracks[propName]) {
this._tracks[propName] = [];
// If time is 0
// Then props is given initialize value
// Else
// Initialize value from current prop value
if (time !== 0) {
this._tracks[propName].push({
time: 0,
value: Animation._cloneValue(
this._getter(this._target, propName)
)
});
}
}
this._tracks[propName].push({
time: parseInt(time, 10),
value: props[propName]
});
}
return this;
}
/**
* @function Zondy.LevelRenderer.Animation.Animator.prototype.during
* @description 添加动画每一帧的回调函数
* @param {RequestCallback} callback - 回调函数
* @returns {Zondy.LevelRenderer.Animation.Animator} Animator
*/
during(callback) {
this._onframeList.push(callback);
return this;
}
/**
* @function Zondy.LevelRenderer.Animation.Animator.prototype.start
* @description 开始执行动画
* @param {(string|function)} easing - 动画缓动函数。详见:<{@link Zondy.LevelRenderer.Animation.easing}>。
* @returns {Zondy.LevelRenderer.Animation.Animator} Animator
*/
start(easing) {
var self = this;
var setter = this._setter;
var getter = this._getter;
var onFrameListLen = self._onframeList.length;
var useSpline = easing === 'spline';
var ondestroy = function () {
self._clipCount--;
if (self._clipCount === 0) {
// Clear all tracks
self._tracks = {};
var len = self._doneList.length;
for (var i = 0; i < len; i++) {
self._doneList[i].call(self);
}
}
};
var createTrackClip = function (keyframes, propName) {
var trackLen = keyframes.length;
if (!trackLen) {
return;
}
// Guess data type
var firstVal = keyframes[0].value;
var isValueArray = Animation._isArrayLike(firstVal);
var isValueColor = false;
// For vertices morphing
var arrDim = (
isValueArray
&& Animation._isArrayLike(firstVal[0])
)
? 2 : 1;
// Sort keyframe as ascending
keyframes.sort(function (a, b) {
return a.time - b.time;
});
var trackMaxTime = keyframes[trackLen - 1].time;
// Percents of each keyframe
var kfPercents = [];
// Value of each keyframe
var kfValues = [];
for (let 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;
if (typeof (value) == 'string') {
value = SUtil.Util_color.toArray(value);
if (value.length === 0) { // Invalid color
value[0] = value[1] = value[2] = 0;
value[3] = 1;
}
isValueColor = true;
}
kfValues.push(value);
}
// Cache the key of last frame to speed up when
// animation playback is sequency
var cacheKey = 0;
var cachePercent = 0;
var start;
var i;
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
if (percent < cachePercent) {
// Start from next key
start = Math.min(cacheKey + 1, trackLen - 1);
for (i = start; i >= 0; i--) {
if (kfPercents[i] <= percent) {
break;
}
}
i = Math.min(i, trackLen - 2);
} else {
for (i = cacheKey; i < trackLen; i++) {
if (kfPercents[i] > percent) {
break;
}
}
i = Math.min(i - 1, trackLen - 2);
}
cacheKey = i;
cachePercent = percent;
var range = (kfPercents[i + 1] - kfPercents[i]);
if (range === 0) {
return;
} else {
w = (percent - kfPercents[i]) / range;
}
if (useSpline) {
p1 = kfValues[i];
p0 = kfValues[i === 0 ? i : i - 1];
p2 = kfValues[i > trackLen - 2 ? trackLen - 1 : i + 1];
p3 = kfValues[i > trackLen - 3 ? trackLen - 1 : i + 2];
if (isValueArray) {
Animation._catmullRomInterpolateArray(
p0, p1, p2, p3, w, w * w, w * w * w,
getter(target, propName),
arrDim
);
} else {
let value;
if (isValueColor) {
// value = Zondy.LevelRenderer.Animation._catmullRomInterpolateArray(
// p0, p1, p2, p3, w, w * w, w * w * w,
// rgba, 1
// );
value = Animation.rgba2String(rgba);
} else {
value = Animation._catmullRomInterpolate(
p0, p1, p2, p3, w, w * w, w * w * w
);
}
setter(
target,
propName,
value
);
}
} else {
if (isValueArray) {
Animation._interpolateArray(
kfValues[i], kfValues[i + 1], w,
getter(target, propName),
arrDim
);
} else {
let value;
if (isValueColor) {
Animation._interpolateArray(
kfValues[i], kfValues[i + 1], w,
rgba, 1
);
value = Animation.rgba2String(rgba);
} else {
value = Animation._interpolateNumber(kfValues[i], kfValues[i + 1], w);
}
setter(
target,
propName,
value
);
}
}
for (i = 0; i < onFrameListLen; i++) {
self._onframeList[i](target, percent);
}
};
var clip = new Clip({
target: self._target,
life: trackMaxTime,
loop: self._loop,
delay: self._delay,
onframe: onframe,
ondestroy: ondestroy
});
if (easing && easing !== 'spline') {
clip.easing = easing;
}
self._clipList.push(clip);
self._clipCount++;
self.animation.add(clip);
};
for (var propName in this._tracks) {
createTrackClip(this._tracks[propName], propName);
}
return this;
}
/**
* @function Zondy.LevelRenderer.Animation.Animator.prototype.stop
* @description 停止动画
*/
stop() {
for (var i = 0; i < this._clipList.length; i++) {
var clip = this._clipList[i];
this.animation.remove(clip);
}
this._clipList = [];
}
/**
* @function Zondy.LevelRenderer.Animation.Animator.prototype.delay
* @description 设置动画延迟开始的时间
* @param {number} time - 时间,单位ms
* @returns {Zondy.LevelRenderer.Animation.Animator} Animator
*/
delay(time) {
this._delay = time;
return this;
}
/**
* @function Zondy.LevelRenderer.Animation.Animator.prototype.done
* @description 添加动画结束的回调
* @param {function} cb - Function
* @returns {Zondy.LevelRenderer.Animation.Animator} Animator
*/
done(cb) {
if (cb) {
this._doneList.push(cb);
}
return this;
}
}
export {Animation};
Zondy.LevelRenderer.Animation = Animation;
export {Animator};
Zondy.LevelRenderer.Animation.Animator = Animator;