track.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. var Clip = require("./Clip");
  2. var _util = require("../core/util");
  3. var isArrayLike = _util.isArrayLike;
  4. var color = require("../tool/color");
  5. var arraySlice = Array.prototype.slice;
  6. /**
  7. * @param {Object} target
  8. * @param {string} propName
  9. * @param {Array.<Object>} keyframes
  10. * [{
  11. * time: number,
  12. * value: number | color string | Array.<number> | Array.<Array.<number>>
  13. * }, ...]
  14. * [Caveat]:
  15. * (1) The order should ensured by time.
  16. * (2) If `value` is `Array`, it must not be shared outside (espaciall el.shape, el.style),
  17. * in case that it be modified outside and cause incorrect interpolate result.
  18. * @param {string} easing
  19. * @param {boolean} [delay=false]
  20. * @param {boolean} [loop=false]
  21. * @param {boolean} [forceAnimate=false]
  22. * @param {Function} [getter=defaultGetter]
  23. * @param {Function} [setter=defaultSetter]
  24. * @return {module:zrender/animation/Clip} clip
  25. */
  26. function createTrackClip(target, propName, keyframes, easing, delay, loop, forceAnimate, getter, setter) {
  27. var useSpline = easing === 'spline';
  28. getter = getter || defaultGetter;
  29. setter = setter || defaultSetter;
  30. var trackLen = keyframes.length;
  31. if (!trackLen) {
  32. return;
  33. } // Guess data type
  34. var firstVal = keyframes[0].value;
  35. var isValueArray = isArrayLike(firstVal);
  36. var isValueColor = false;
  37. var isValueString = false; // For vertices morphing
  38. var arrDim = isValueArray ? getArrayDim(keyframes) : 0;
  39. var trackMaxTime;
  40. trackMaxTime = keyframes[trackLen - 1].time; // Percents of each keyframe
  41. var kfPercents = []; // Value of each keyframe
  42. var kfValues = [];
  43. var prevValue = keyframes[0].value;
  44. var isAllValueEqual = true;
  45. for (var i = 0; i < trackLen; i++) {
  46. kfPercents.push(keyframes[i].time / trackMaxTime); // Assume value is a color when it is a string
  47. var value = keyframes[i].value; // Check if value is equal, deep check if value is array
  48. if (!(isValueArray && isArraySame(value, prevValue, arrDim) || !isValueArray && value === prevValue)) {
  49. isAllValueEqual = false;
  50. }
  51. prevValue = value; // Try converting a string to a color array
  52. if (typeof value === 'string') {
  53. var colorArray = color.parse(value);
  54. if (colorArray) {
  55. value = colorArray;
  56. isValueColor = true;
  57. } else {
  58. isValueString = true;
  59. }
  60. }
  61. kfValues.push(value);
  62. }
  63. if (!forceAnimate && isAllValueEqual) {
  64. return;
  65. }
  66. var lastValue = kfValues[trackLen - 1]; // Polyfill array and NaN value
  67. for (var i = 0; i < trackLen - 1; i++) {
  68. if (isValueArray) {
  69. fillArr(kfValues[i], lastValue, arrDim);
  70. } else {
  71. if (isNaN(kfValues[i]) && !isNaN(lastValue) && !isValueString && !isValueColor) {
  72. kfValues[i] = lastValue;
  73. }
  74. }
  75. }
  76. isValueArray && fillArr(getter(target, propName), lastValue, arrDim); // Cache the key of last frame to speed up when
  77. // animation playback is sequency
  78. var lastFrame = 0;
  79. var lastFramePercent = 0;
  80. var start;
  81. var w;
  82. var p0;
  83. var p1;
  84. var p2;
  85. var p3;
  86. if (isValueColor) {
  87. var rgba = [0, 0, 0, 0];
  88. }
  89. function hanleFrame(target, percent) {
  90. // Find the range keyframes
  91. // kf1-----kf2---------current--------kf3
  92. // find kf2 and kf3 and do interpolation
  93. var frame; // In the easing function like elasticOut, percent may less than 0
  94. if (percent < 0) {
  95. frame = 0;
  96. } else if (percent < lastFramePercent) {
  97. // Start from next key
  98. // PENDING start from lastFrame ?
  99. start = Math.min(lastFrame + 1, trackLen - 1);
  100. for (frame = start; frame >= 0; frame--) {
  101. if (kfPercents[frame] <= percent) {
  102. break;
  103. }
  104. } // PENDING really need to do this ?
  105. frame = Math.min(frame, trackLen - 2);
  106. } else {
  107. for (frame = lastFrame; frame < trackLen; frame++) {
  108. if (kfPercents[frame] > percent) {
  109. break;
  110. }
  111. }
  112. frame = Math.min(frame - 1, trackLen - 2);
  113. }
  114. lastFrame = frame;
  115. lastFramePercent = percent;
  116. var range = kfPercents[frame + 1] - kfPercents[frame];
  117. if (range === 0) {
  118. return;
  119. } else {
  120. w = (percent - kfPercents[frame]) / range;
  121. }
  122. if (useSpline) {
  123. p1 = kfValues[frame];
  124. p0 = kfValues[frame === 0 ? frame : frame - 1];
  125. p2 = kfValues[frame > trackLen - 2 ? trackLen - 1 : frame + 1];
  126. p3 = kfValues[frame > trackLen - 3 ? trackLen - 1 : frame + 2];
  127. if (isValueArray) {
  128. catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, getter(target, propName), arrDim);
  129. } else {
  130. var value;
  131. if (isValueColor) {
  132. value = catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, rgba, 1);
  133. value = rgba2String(rgba);
  134. } else if (isValueString) {
  135. // String is step(0.5)
  136. return interpolateString(p1, p2, w);
  137. } else {
  138. value = catmullRomInterpolate(p0, p1, p2, p3, w, w * w, w * w * w);
  139. }
  140. setter(target, propName, value);
  141. }
  142. } else {
  143. if (isValueArray) {
  144. interpolateArray(kfValues[frame], kfValues[frame + 1], w, getter(target, propName), arrDim);
  145. } else {
  146. var value;
  147. if (isValueColor) {
  148. interpolateArray(kfValues[frame], kfValues[frame + 1], w, rgba, 1);
  149. value = rgba2String(rgba);
  150. } else if (isValueString) {
  151. // String is step(0.5)
  152. return interpolateString(kfValues[frame], kfValues[frame + 1], w);
  153. } else {
  154. value = interpolateNumber(kfValues[frame], kfValues[frame + 1], w);
  155. }
  156. if (target.aaaa != null) {
  157. console.log(target.uuid, value, propName);
  158. }
  159. setter(target, propName, value);
  160. }
  161. }
  162. }
  163. var clip = new Clip({
  164. target: target,
  165. life: trackMaxTime,
  166. loop: loop,
  167. delay: delay,
  168. onframe: hanleFrame
  169. });
  170. if (easing && easing !== 'spline') {
  171. clip.easing = easing;
  172. }
  173. return clip;
  174. }
  175. function getArrayDim(keyframes) {
  176. var lastValue = keyframes[keyframes.length - 1].value;
  177. return isArrayLike(lastValue && lastValue[0]) ? 2 : 1;
  178. }
  179. /**
  180. * @param {Array} arr0
  181. * @param {Array} arr1
  182. * @param {number} arrDim
  183. * @return {boolean}
  184. */
  185. function isArraySame(arr0, arr1, arrDim) {
  186. if (arr0 === arr1) {
  187. return true;
  188. }
  189. var len = arr0.length;
  190. if (len !== arr1.length) {
  191. return false;
  192. }
  193. if (arrDim === 1) {
  194. for (var i = 0; i < len; i++) {
  195. if (arr0[i] !== arr1[i]) {
  196. return false;
  197. }
  198. }
  199. } else {
  200. var len2 = arr0[0].length;
  201. for (var i = 0; i < len; i++) {
  202. for (var j = 0; j < len2; j++) {
  203. if (arr0[i][j] !== arr1[i][j]) {
  204. return false;
  205. }
  206. }
  207. }
  208. }
  209. return true;
  210. } // arr0 is source array, arr1 is target array.
  211. // Do some preprocess to avoid error happened when interpolating from arr0 to arr1
  212. function fillArr(arr0, arr1, arrDim) {
  213. var arr0Len = arr0.length;
  214. var arr1Len = arr1.length;
  215. if (arr0Len !== arr1Len) {
  216. // FIXME Not work for TypedArray
  217. var isPreviousLarger = arr0Len > arr1Len;
  218. if (isPreviousLarger) {
  219. // Cut the previous
  220. arr0.length = arr1Len;
  221. } else {
  222. // Fill the previous
  223. for (var i = arr0Len; i < arr1Len; i++) {
  224. arr0.push(arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i]));
  225. }
  226. }
  227. } // Handling NaN value
  228. var len2 = arr0[0] && arr0[0].length;
  229. for (var i = 0; i < arr0.length; i++) {
  230. if (arrDim === 1) {
  231. if (isNaN(arr0[i])) {
  232. arr0[i] = arr1[i];
  233. }
  234. } else {
  235. for (var j = 0; j < len2; j++) {
  236. if (isNaN(arr0[i][j])) {
  237. arr0[i][j] = arr1[i][j];
  238. }
  239. }
  240. }
  241. }
  242. }
  243. /**
  244. * Catmull Rom interpolate array
  245. * @param {Array} p0
  246. * @param {Array} p1
  247. * @param {Array} p2
  248. * @param {Array} p3
  249. * @param {number} t
  250. * @param {number} t2
  251. * @param {number} t3
  252. * @param {Array} out
  253. * @param {number} arrDim
  254. */
  255. function catmullRomInterpolateArray(p0, p1, p2, p3, t, t2, t3, out, arrDim) {
  256. var len = p0.length;
  257. if (arrDim === 1) {
  258. for (var i = 0; i < len; i++) {
  259. out[i] = catmullRomInterpolate(p0[i], p1[i], p2[i], p3[i], t, t2, t3);
  260. }
  261. } else {
  262. var len2 = p0[0].length;
  263. for (var i = 0; i < len; i++) {
  264. for (var j = 0; j < len2; j++) {
  265. out[i][j] = catmullRomInterpolate(p0[i][j], p1[i][j], p2[i][j], p3[i][j], t, t2, t3);
  266. }
  267. }
  268. }
  269. }
  270. /**
  271. * Catmull Rom interpolate number
  272. * @param {number} p0
  273. * @param {number} p1
  274. * @param {number} p2
  275. * @param {number} p3
  276. * @param {number} t
  277. * @param {number} t2
  278. * @param {number} t3
  279. * @return {number}
  280. */
  281. function catmullRomInterpolate(p0, p1, p2, p3, t, t2, t3) {
  282. var v0 = (p2 - p0) * 0.5;
  283. var v1 = (p3 - p1) * 0.5;
  284. return (2 * (p1 - p2) + v0 + v1) * t3 + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + v0 * t + p1;
  285. }
  286. /**
  287. * @param {number} p0
  288. * @param {number} p1
  289. * @param {number} percent
  290. * @return {number}
  291. */
  292. function interpolateNumber(p0, p1, percent) {
  293. return (p1 - p0) * percent + p0;
  294. }
  295. /**
  296. * @param {string} p0
  297. * @param {string} p1
  298. * @param {number} percent
  299. * @return {string}
  300. */
  301. function interpolateString(p0, p1, percent) {
  302. return percent > 0.5 ? p1 : p0;
  303. }
  304. /**
  305. * @param {Array} p0
  306. * @param {Array} p1
  307. * @param {number} percent
  308. * @param {Array} out
  309. * @param {number} arrDim
  310. */
  311. function interpolateArray(p0, p1, percent, out, arrDim) {
  312. var len = p0.length;
  313. if (arrDim === 1) {
  314. for (var i = 0; i < len; i++) {
  315. out[i] = interpolateNumber(p0[i], p1[i], percent);
  316. }
  317. } else {
  318. var len2 = len && p0[0].length;
  319. for (var i = 0; i < len; i++) {
  320. for (var j = 0; j < len2; j++) {
  321. out[i][j] = interpolateNumber(p0[i][j], p1[i][j], percent);
  322. }
  323. }
  324. }
  325. }
  326. function rgba2String(rgba) {
  327. rgba[0] = Math.floor(rgba[0]);
  328. rgba[1] = Math.floor(rgba[1]);
  329. rgba[2] = Math.floor(rgba[2]);
  330. return 'rgba(' + rgba.join(',') + ')';
  331. }
  332. function cloneFrameValue(value) {
  333. if (isArrayLike(value)) {
  334. var len = value.length;
  335. if (isArrayLike(value[0])) {
  336. var ret = [];
  337. for (var i = 0; i < len; i++) {
  338. ret.push(arraySlice.call(value[i]));
  339. }
  340. return ret;
  341. }
  342. return arraySlice.call(value);
  343. }
  344. return value;
  345. }
  346. function defaultGetter(target, key) {
  347. return target[key];
  348. }
  349. function defaultSetter(target, key, value) {
  350. target[key] = value;
  351. }
  352. exports.createTrackClip = createTrackClip;
  353. exports.cloneFrameValue = cloneFrameValue;
  354. exports.defaultGetter = defaultGetter;
  355. exports.defaultSetter = defaultSetter;