import Clip from './Clip.js'; import * as color from '../tool/color.js'; import { eqNaN, extend, isArrayLike, isFunction, isGradientObject, isNumber, isString, keys, logError, map } from '../core/util.js'; import easingFuncs from './easing.js'; import { createCubicEasingFunc } from './cubicEasing.js'; import { isLinearGradient, isRadialGradient } from '../svg/helper.js'; ; var arraySlice = Array.prototype.slice; function interpolateNumber(p0, p1, percent) { return (p1 - p0) * percent + p0; } function interpolate1DArray(out, p0, p1, percent) { var len = p0.length; for (var i = 0; i < len; i++) { out[i] = interpolateNumber(p0[i], p1[i], percent); } return out; } function interpolate2DArray(out, p0, p1, percent) { var len = p0.length; var len2 = len && p0[0].length; for (var i = 0; i < len; i++) { if (!out[i]) { out[i] = []; } for (var j = 0; j < len2; j++) { out[i][j] = interpolateNumber(p0[i][j], p1[i][j], percent); } } return out; } function add1DArray(out, p0, p1, sign) { var len = p0.length; for (var i = 0; i < len; i++) { out[i] = p0[i] + p1[i] * sign; } return out; } function add2DArray(out, p0, p1, sign) { var len = p0.length; var len2 = len && p0[0].length; for (var i = 0; i < len; i++) { if (!out[i]) { out[i] = []; } for (var j = 0; j < len2; j++) { out[i][j] = p0[i][j] + p1[i][j] * sign; } } return out; } function fillColorStops(val0, val1) { var len0 = val0.length; var len1 = val1.length; var shorterArr = len0 > len1 ? val1 : val0; var shorterLen = Math.min(len0, len1); var last = shorterArr[shorterLen - 1] || { color: [0, 0, 0, 0], offset: 0 }; for (var i = shorterLen; i < Math.max(len0, len1); i++) { shorterArr.push({ offset: last.offset, color: last.color.slice() }); } } function fillArray(val0, val1, arrDim) { var arr0 = val0; var arr1 = val1; if (!arr0.push || !arr1.push) { return; } var arr0Len = arr0.length; var arr1Len = arr1.length; if (arr0Len !== arr1Len) { var isPreviousLarger = arr0Len > arr1Len; if (isPreviousLarger) { arr0.length = arr1Len; } else { for (var i = arr0Len; i < arr1Len; i++) { arr0.push(arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i])); } } } 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]; } } } } } export 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]) || 0; rgba[1] = Math.floor(rgba[1]) || 0; rgba[2] = Math.floor(rgba[2]) || 0; rgba[3] = rgba[3] == null ? 1 : rgba[3]; return 'rgba(' + rgba.join(',') + ')'; } function guessArrayDim(value) { return isArrayLike(value && value[0]) ? 2 : 1; } var VALUE_TYPE_NUMBER = 0; var VALUE_TYPE_1D_ARRAY = 1; var VALUE_TYPE_2D_ARRAY = 2; var VALUE_TYPE_COLOR = 3; var VALUE_TYPE_LINEAR_GRADIENT = 4; var VALUE_TYPE_RADIAL_GRADIENT = 5; var VALUE_TYPE_UNKOWN = 6; function isGradientValueType(valType) { return valType === VALUE_TYPE_LINEAR_GRADIENT || valType === VALUE_TYPE_RADIAL_GRADIENT; } function isArrayValueType(valType) { return valType === VALUE_TYPE_1D_ARRAY || valType === VALUE_TYPE_2D_ARRAY; } var tmpRgba = [0, 0, 0, 0]; var Track = (function () { function Track(propName) { this.keyframes = []; this.discrete = false; this._invalid = false; this._needsSort = false; this._lastFr = 0; this._lastFrP = 0; this.propName = propName; } Track.prototype.isFinished = function () { return this._finished; }; Track.prototype.setFinished = function () { this._finished = true; if (this._additiveTrack) { this._additiveTrack.setFinished(); } }; Track.prototype.needsAnimate = function () { return this.keyframes.length >= 1; }; Track.prototype.getAdditiveTrack = function () { return this._additiveTrack; }; Track.prototype.addKeyframe = function (time, rawValue, easing) { this._needsSort = true; var keyframes = this.keyframes; var len = keyframes.length; var discrete = false; var valType = VALUE_TYPE_UNKOWN; var value = rawValue; if (isArrayLike(rawValue)) { var arrayDim = guessArrayDim(rawValue); valType = arrayDim; if (arrayDim === 1 && !isNumber(rawValue[0]) || arrayDim === 2 && !isNumber(rawValue[0][0])) { discrete = true; } } else { if (isNumber(rawValue) && !eqNaN(rawValue)) { valType = VALUE_TYPE_NUMBER; } else if (isString(rawValue)) { if (!isNaN(+rawValue)) { valType = VALUE_TYPE_NUMBER; } else { var colorArray = color.parse(rawValue); if (colorArray) { value = colorArray; valType = VALUE_TYPE_COLOR; } } } else if (isGradientObject(rawValue)) { var parsedGradient = extend({}, value); parsedGradient.colorStops = map(rawValue.colorStops, function (colorStop) { return ({ offset: colorStop.offset, color: color.parse(colorStop.color) }); }); if (isLinearGradient(rawValue)) { valType = VALUE_TYPE_LINEAR_GRADIENT; } else if (isRadialGradient(rawValue)) { valType = VALUE_TYPE_RADIAL_GRADIENT; } value = parsedGradient; } } if (len === 0) { this.valType = valType; } else if (valType !== this.valType || valType === VALUE_TYPE_UNKOWN) { discrete = true; } this.discrete = this.discrete || discrete; var kf = { time: time, value: value, rawValue: rawValue, percent: 0 }; if (easing) { kf.easing = easing; kf.easingFunc = isFunction(easing) ? easing : easingFuncs[easing] || createCubicEasingFunc(easing); } keyframes.push(kf); return kf; }; Track.prototype.prepare = function (maxTime, additiveTrack) { var kfs = this.keyframes; if (this._needsSort) { kfs.sort(function (a, b) { return a.time - b.time; }); } var valType = this.valType; var kfsLen = kfs.length; var lastKf = kfs[kfsLen - 1]; var isDiscrete = this.discrete; var isArr = isArrayValueType(valType); var isGradient = isGradientValueType(valType); for (var i = 0; i < kfsLen; i++) { var kf = kfs[i]; var value = kf.value; var lastValue = lastKf.value; kf.percent = kf.time / maxTime; if (!isDiscrete) { if (isArr && i !== kfsLen - 1) { fillArray(value, lastValue, valType); } else if (isGradient) { fillColorStops(value.colorStops, lastValue.colorStops); } } } if (!isDiscrete && valType !== VALUE_TYPE_RADIAL_GRADIENT && additiveTrack && this.needsAnimate() && additiveTrack.needsAnimate() && valType === additiveTrack.valType && !additiveTrack._finished) { this._additiveTrack = additiveTrack; var startValue = kfs[0].value; for (var i = 0; i < kfsLen; i++) { if (valType === VALUE_TYPE_NUMBER) { kfs[i].additiveValue = kfs[i].value - startValue; } else if (valType === VALUE_TYPE_COLOR) { kfs[i].additiveValue = add1DArray([], kfs[i].value, startValue, -1); } else if (isArrayValueType(valType)) { kfs[i].additiveValue = valType === VALUE_TYPE_1D_ARRAY ? add1DArray([], kfs[i].value, startValue, -1) : add2DArray([], kfs[i].value, startValue, -1); } } } }; Track.prototype.step = function (target, percent) { if (this._finished) { return; } if (this._additiveTrack && this._additiveTrack._finished) { this._additiveTrack = null; } var isAdditive = this._additiveTrack != null; var valueKey = isAdditive ? 'additiveValue' : 'value'; var valType = this.valType; var keyframes = this.keyframes; var kfsNum = keyframes.length; var propName = this.propName; var isValueColor = valType === VALUE_TYPE_COLOR; var frameIdx; var lastFrame = this._lastFr; var mathMin = Math.min; var frame; var nextFrame; if (kfsNum === 1) { frame = nextFrame = keyframes[0]; } else { if (percent < 0) { frameIdx = 0; } else if (percent < this._lastFrP) { var start = mathMin(lastFrame + 1, kfsNum - 1); for (frameIdx = start; frameIdx >= 0; frameIdx--) { if (keyframes[frameIdx].percent <= percent) { break; } } frameIdx = mathMin(frameIdx, kfsNum - 2); } else { for (frameIdx = lastFrame; frameIdx < kfsNum; frameIdx++) { if (keyframes[frameIdx].percent > percent) { break; } } frameIdx = mathMin(frameIdx - 1, kfsNum - 2); } nextFrame = keyframes[frameIdx + 1]; frame = keyframes[frameIdx]; } if (!(frame && nextFrame)) { return; } this._lastFr = frameIdx; this._lastFrP = percent; var interval = (nextFrame.percent - frame.percent); var w = interval === 0 ? 1 : mathMin((percent - frame.percent) / interval, 1); if (nextFrame.easingFunc) { w = nextFrame.easingFunc(w); } var targetArr = isAdditive ? this._additiveValue : (isValueColor ? tmpRgba : target[propName]); if ((isArrayValueType(valType) || isValueColor) && !targetArr) { targetArr = this._additiveValue = []; } if (this.discrete) { target[propName] = w < 1 ? frame.rawValue : nextFrame.rawValue; } else if (isArrayValueType(valType)) { valType === VALUE_TYPE_1D_ARRAY ? interpolate1DArray(targetArr, frame[valueKey], nextFrame[valueKey], w) : interpolate2DArray(targetArr, frame[valueKey], nextFrame[valueKey], w); } else if (isGradientValueType(valType)) { var val = frame[valueKey]; var nextVal_1 = nextFrame[valueKey]; var isLinearGradient_1 = valType === VALUE_TYPE_LINEAR_GRADIENT; target[propName] = { type: isLinearGradient_1 ? 'linear' : 'radial', x: interpolateNumber(val.x, nextVal_1.x, w), y: interpolateNumber(val.y, nextVal_1.y, w), colorStops: map(val.colorStops, function (colorStop, idx) { var nextColorStop = nextVal_1.colorStops[idx]; return { offset: interpolateNumber(colorStop.offset, nextColorStop.offset, w), color: rgba2String(interpolate1DArray([], colorStop.color, nextColorStop.color, w)) }; }), global: nextVal_1.global }; if (isLinearGradient_1) { target[propName].x2 = interpolateNumber(val.x2, nextVal_1.x2, w); target[propName].y2 = interpolateNumber(val.y2, nextVal_1.y2, w); } else { target[propName].r = interpolateNumber(val.r, nextVal_1.r, w); } } else if (isValueColor) { interpolate1DArray(targetArr, frame[valueKey], nextFrame[valueKey], w); if (!isAdditive) { target[propName] = rgba2String(targetArr); } } else { var value = interpolateNumber(frame[valueKey], nextFrame[valueKey], w); if (isAdditive) { this._additiveValue = value; } else { target[propName] = value; } } if (isAdditive) { this._addToTarget(target); } }; Track.prototype._addToTarget = function (target) { var valType = this.valType; var propName = this.propName; var additiveValue = this._additiveValue; if (valType === VALUE_TYPE_NUMBER) { target[propName] = target[propName] + additiveValue; } else if (valType === VALUE_TYPE_COLOR) { color.parse(target[propName], tmpRgba); add1DArray(tmpRgba, tmpRgba, additiveValue, 1); target[propName] = rgba2String(tmpRgba); } else if (valType === VALUE_TYPE_1D_ARRAY) { add1DArray(target[propName], target[propName], additiveValue, 1); } else if (valType === VALUE_TYPE_2D_ARRAY) { add2DArray(target[propName], target[propName], additiveValue, 1); } }; return Track; }()); var Animator = (function () { function Animator(target, loop, allowDiscreteAnimation, additiveTo) { this._tracks = {}; this._trackKeys = []; this._maxTime = 0; this._started = 0; this._clip = null; this._target = target; this._loop = loop; if (loop && additiveTo) { logError('Can\' use additive animation on looped animation.'); return; } this._additiveAnimators = additiveTo; this._allowDiscrete = allowDiscreteAnimation; } Animator.prototype.getMaxTime = function () { return this._maxTime; }; Animator.prototype.getDelay = function () { return this._delay; }; Animator.prototype.getLoop = function () { return this._loop; }; Animator.prototype.getTarget = function () { return this._target; }; Animator.prototype.changeTarget = function (target) { this._target = target; }; Animator.prototype.when = function (time, props, easing) { return this.whenWithKeys(time, props, keys(props), easing); }; Animator.prototype.whenWithKeys = function (time, props, propNames, easing) { var tracks = this._tracks; for (var i = 0; i < propNames.length; i++) { var propName = propNames[i]; var track = tracks[propName]; if (!track) { track = tracks[propName] = new Track(propName); var initialValue = void 0; var additiveTrack = this._getAdditiveTrack(propName); if (additiveTrack) { var addtiveTrackKfs = additiveTrack.keyframes; var lastFinalKf = addtiveTrackKfs[addtiveTrackKfs.length - 1]; initialValue = lastFinalKf && lastFinalKf.value; if (additiveTrack.valType === VALUE_TYPE_COLOR && initialValue) { initialValue = rgba2String(initialValue); } } else { initialValue = this._target[propName]; } if (initialValue == null) { continue; } if (time > 0) { track.addKeyframe(0, cloneValue(initialValue), easing); } this._trackKeys.push(propName); } track.addKeyframe(time, cloneValue(props[propName]), easing); } this._maxTime = Math.max(this._maxTime, time); return this; }; Animator.prototype.pause = function () { this._clip.pause(); this._paused = true; }; Animator.prototype.resume = function () { this._clip.resume(); this._paused = false; }; Animator.prototype.isPaused = function () { return !!this._paused; }; Animator.prototype.duration = function (duration) { this._maxTime = duration; this._force = true; return this; }; Animator.prototype._doneCallback = function () { this._setTracksFinished(); this._clip = null; var doneList = this._doneCbs; if (doneList) { var len = doneList.length; for (var i = 0; i < len; i++) { doneList[i].call(this); } } }; Animator.prototype._abortedCallback = function () { this._setTracksFinished(); var animation = this.animation; var abortedList = this._abortedCbs; if (animation) { animation.removeClip(this._clip); } this._clip = null; if (abortedList) { for (var i = 0; i < abortedList.length; i++) { abortedList[i].call(this); } } }; Animator.prototype._setTracksFinished = function () { var tracks = this._tracks; var tracksKeys = this._trackKeys; for (var i = 0; i < tracksKeys.length; i++) { tracks[tracksKeys[i]].setFinished(); } }; Animator.prototype._getAdditiveTrack = function (trackName) { var additiveTrack; var additiveAnimators = this._additiveAnimators; if (additiveAnimators) { for (var i = 0; i < additiveAnimators.length; i++) { var track = additiveAnimators[i].getTrack(trackName); if (track) { additiveTrack = track; } } } return additiveTrack; }; Animator.prototype.start = function (easing) { if (this._started > 0) { return; } this._started = 1; var self = this; var tracks = []; var maxTime = this._maxTime || 0; for (var i = 0; i < this._trackKeys.length; i++) { var propName = this._trackKeys[i]; var track = this._tracks[propName]; var additiveTrack = this._getAdditiveTrack(propName); var kfs = track.keyframes; var kfsNum = kfs.length; track.prepare(maxTime, additiveTrack); if (track.needsAnimate()) { if (!this._allowDiscrete && track.discrete) { var lastKf = kfs[kfsNum - 1]; if (lastKf) { self._target[track.propName] = lastKf.rawValue; } track.setFinished(); } else { tracks.push(track); } } } if (tracks.length || this._force) { var clip = new Clip({ life: maxTime, loop: this._loop, delay: this._delay || 0, onframe: function (percent) { self._started = 2; var additiveAnimators = self._additiveAnimators; if (additiveAnimators) { var stillHasAdditiveAnimator = false; for (var i = 0; i < additiveAnimators.length; i++) { if (additiveAnimators[i]._clip) { stillHasAdditiveAnimator = true; break; } } if (!stillHasAdditiveAnimator) { self._additiveAnimators = null; } } for (var i = 0; i < tracks.length; i++) { tracks[i].step(self._target, percent); } var onframeList = self._onframeCbs; if (onframeList) { for (var i = 0; i < onframeList.length; i++) { onframeList[i](self._target, percent); } } }, ondestroy: function () { self._doneCallback(); } }); this._clip = clip; if (this.animation) { this.animation.addClip(clip); } if (easing) { clip.setEasing(easing); } } else { this._doneCallback(); } return this; }; Animator.prototype.stop = function (forwardToLast) { if (!this._clip) { return; } var clip = this._clip; if (forwardToLast) { clip.onframe(1); } this._abortedCallback(); }; Animator.prototype.delay = function (time) { this._delay = time; return this; }; Animator.prototype.during = function (cb) { if (cb) { if (!this._onframeCbs) { this._onframeCbs = []; } this._onframeCbs.push(cb); } return this; }; Animator.prototype.done = function (cb) { if (cb) { if (!this._doneCbs) { this._doneCbs = []; } this._doneCbs.push(cb); } return this; }; Animator.prototype.aborted = function (cb) { if (cb) { if (!this._abortedCbs) { this._abortedCbs = []; } this._abortedCbs.push(cb); } return this; }; Animator.prototype.getClip = function () { return this._clip; }; Animator.prototype.getTrack = function (propName) { return this._tracks[propName]; }; Animator.prototype.getTracks = function () { var _this = this; return map(this._trackKeys, function (key) { return _this._tracks[key]; }); }; Animator.prototype.stopTracks = function (propNames, forwardToLast) { if (!propNames.length || !this._clip) { return true; } var tracks = this._tracks; var tracksKeys = this._trackKeys; for (var i = 0; i < propNames.length; i++) { var track = tracks[propNames[i]]; if (track && !track.isFinished()) { if (forwardToLast) { track.step(this._target, 1); } else if (this._started === 1) { track.step(this._target, 0); } track.setFinished(); } } var allAborted = true; for (var i = 0; i < tracksKeys.length; i++) { if (!tracks[tracksKeys[i]].isFinished()) { allAborted = false; break; } } if (allAborted) { this._abortedCallback(); } return allAborted; }; Animator.prototype.saveTo = function (target, trackKeys, firstOrLast) { if (!target) { return; } trackKeys = trackKeys || this._trackKeys; for (var i = 0; i < trackKeys.length; i++) { var propName = trackKeys[i]; var track = this._tracks[propName]; if (!track || track.isFinished()) { continue; } var kfs = track.keyframes; var kf = kfs[firstOrLast ? 0 : kfs.length - 1]; if (kf) { target[propName] = cloneValue(kf.rawValue); } } }; Animator.prototype.__changeFinalValue = function (finalProps, trackKeys) { trackKeys = trackKeys || keys(finalProps); for (var i = 0; i < trackKeys.length; i++) { var propName = trackKeys[i]; var track = this._tracks[propName]; if (!track) { continue; } var kfs = track.keyframes; if (kfs.length > 1) { var lastKf = kfs.pop(); track.addKeyframe(lastKf.time, finalProps[propName]); track.prepare(this._maxTime, track.getAdditiveTrack()); } } }; return Animator; }()); export default Animator;