var colorUtil = require("../utils/color_util"); var dataUtil = require("../utils/data_structure_util"); var Timeline = require("./Timeline"); var _constants = require("../utils/constants"); var mathMin = _constants.mathMin; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } /** * @class qrenderer.animation.Track * There is an one-to-one correspondence between a track and a property of an element. * There are many properties on an element, multiple properties can change at the same time during the animation process. * Each property naturally becomes a track, all these changing processes will be encapsulated in the Track class. * * * Track, 轨道,与元素(Element)上可以用来进行动画的属性一一对应。 * 元素上存在很多种属性,在动画过程中,可能会有多种属性同时发生变化, * 每一种属性天然成为一条动画轨道,把这些轨道上的变化过程封装在 Track 类中。 * * @author 大漠穷秋 <damoqiongqiu@126.com> * @docauthor 大漠穷秋 <damoqiongqiu@126.com> */ var Track = /*#__PURE__*/ function () { /** * @method constructor Track * @param {Object} options */ function Track(options) { _classCallCheck(this, Track); this.element = options.element; this.path = options.path; this.delay = options.delay; this.currentValue = null; this.isFinished = false; this.keyframes = []; this.timeline; } /** * @method addKeyFrame * Add a key frame. * * * 添加关键帧。 * @param {Object} kf 数据结构为 {time:0,value:0} */ _createClass(Track, [{ key: "addKeyFrame", value: function addKeyFrame(kf) { this.keyframes.push(kf); } /** * @method nextFrame * Enter the next frame. * * * 进入下一帧。 * @param {Number} time 当前时间 * @param {Number} delta 时间偏移量 */ }, { key: "nextFrame", value: function nextFrame(time, delta) { if (!this.timeline) { return; } var result = this.timeline.nextFrame(time, delta); if (dataUtil.isString(result) && result === 'destroy') { this.isFinished = true; } // console.log(`result=${result}`); return result; } /** * @method fire * Fire an event. * * * 触发事件。 * @param {String} eventType * @param {Object} arg */ }, { key: "fire", value: function fire(eventType, arg) { this.timeline.fire(eventType, arg); } /** * @method start * Start the animation. * * * 开始动画。 * @param {String} easing 缓动函数名称 * @param {Boolean} forceAnimate 是否强制开启动画 */ }, { key: "start", value: function start() { var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var easing = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; var forceAnimate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var options = this._parseKeyFrames(easing, this.path, loop, forceAnimate); //如果传入的参数不正确,则无法构造实例 if (!options) { return null; } var timeline = new Timeline(options); this.timeline = timeline; } /** * @method stop * Stop the animation * * * 停止动画。 * @param {Boolean} forwardToLast 是否快进到最后一帧 */ }, { key: "stop", value: function stop(forwardToLast) { if (forwardToLast) { // Move to last frame before stop this.timeline && this.timeline.onframe(this.element, 1); } } /** * @method pause * Pause the animation * * * 暂停。 */ }, { key: "pause", value: function pause() { this.timeline.pause(); } /** * @method resume * Resume the animation. * * * 重启。 */ }, { key: "resume", value: function resume() { this.timeline.resume(); } /** * @private * @method _parseKeyFrames * Parse the keyframes, create the timelines. * * * 解析关键帧,创建时间线。 * @param {String} easing * @param {String} path * @param {Boolean} forceAnimate */ }, { key: "_parseKeyFrames", value: function _parseKeyFrames(easing, path, loop, forceAnimate) { var self = this; var element = this.element; var useSpline = easing === 'spline'; var kfLength = this.keyframes.length; if (!kfLength) { return; } // Guess data type var firstVal = this.keyframes[0].value; var isValueArray = dataUtil.isArrayLike(firstVal); var isValueColor = false; var isValueString = false; // For vertices morphing var arrDim = isValueArray ? dataUtil.getArrayDim(this.keyframes) : 0; this.keyframes.sort(function (a, b) { return a.time - b.time; }); var trackMaxTime = this.keyframes[kfLength - 1].time; var kfPercents = []; var kfValues = []; var prevValue = this.keyframes[0].value; var isAllValueEqual = true; for (var i = 0; i < kfLength; i++) { kfPercents.push(this.keyframes[i].time / trackMaxTime); // Assume value is a color when it is a string var value = this.keyframes[i].value; // Check if value is equal, deep check if value is array if (!(isValueArray && dataUtil.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 = colorUtil.parse(value); if (colorArray) { value = colorArray; isValueColor = true; } else { isValueString = true; } } kfValues.push(value); } if (!forceAnimate && isAllValueEqual) { return; } var lastValue = kfValues[kfLength - 1]; // Polyfill array and NaN value for (var _i = 0; _i < kfLength - 1; _i++) { if (isValueArray) { dataUtil.fillArr(kfValues[_i], lastValue, arrDim); } else { if (isNaN(kfValues[_i]) && !isNaN(lastValue) && !isValueString && !isValueColor) { kfValues[_i] = lastValue; } } } if (isValueArray) { var arr = dataUtil.getAttrByPath(element, path); dataUtil.fillArr(arr, 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; var rgba = [0, 0, 0, 0]; var onframe = function onframe(element, 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 = mathMin(lastFrame + 1, kfLength - 1); for (frame = start; frame >= 0; frame--) { if (kfPercents[frame] <= percent) { break; } } // PENDING really need to do this ? frame = mathMin(frame, kfLength - 2); } else { for (frame = lastFrame; frame < kfLength; frame++) { if (kfPercents[frame] > percent) { break; } } frame = mathMin(frame - 1, kfLength - 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 > kfLength - 2 ? kfLength - 1 : frame + 1]; p3 = kfValues[frame > kfLength - 3 ? kfLength - 1 : frame + 2]; if (isValueArray) { var _arr = dataUtil.getAttrByPath(element, path); dataUtil.catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, _arr, arrDim); self.currentValue = _arr; element.dirty(); } else { var _value; if (isValueColor) { _value = dataUtil.catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, rgba, 1); _value = dataUtil.rgba2String(rgba); } else if (isValueString) { // String is step(0.5) _value = dataUtil.interpolateString(p1, p2, w); } else { _value = dataUtil.catmullRomInterpolate(p0, p1, p2, p3, w, w * w, w * w * w); } dataUtil.setAttrByPath(element, path, _value); self.currentValue = _value; element.dirty(); } } else { if (isValueArray) { var _arr2 = dataUtil.getAttrByPath(element, path); dataUtil.interpolateArray(kfValues[frame], kfValues[frame + 1], w, _arr2, arrDim); self.currentValue = _arr2; element.dirty(); } else { var _value2; if (isValueColor) { dataUtil.interpolateArray(kfValues[frame], kfValues[frame + 1], w, rgba, 1); _value2 = dataUtil.rgba2String(rgba); } else if (isValueString) { // String is step(0.5) _value2 = dataUtil.interpolateString(kfValues[frame], kfValues[frame + 1], w); } else { _value2 = dataUtil.interpolateNumber(kfValues[frame], kfValues[frame + 1], w); } dataUtil.setAttrByPath(element, path, _value2); self.currentValue = _value2; element.dirty(); } } }; var options = { element: this.element, lifeTime: trackMaxTime, loop: loop, delay: this.delay, onframe: onframe, easing: easing && easing !== 'spline' ? easing : 'Linear' }; return options; } }]); return Track; }(); module.exports = Track;