Animator.ts 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149
  1. /**
  2. * @module echarts/animation/Animator
  3. */
  4. import Clip from './Clip';
  5. import * as color from '../tool/color';
  6. import {
  7. eqNaN,
  8. extend,
  9. isArrayLike,
  10. isFunction,
  11. isGradientObject,
  12. isNumber,
  13. isString,
  14. keys,
  15. logError,
  16. map
  17. } from '../core/util';
  18. import {ArrayLike, Dictionary} from '../core/types';
  19. import easingFuncs, { AnimationEasing } from './easing';
  20. import Animation from './Animation';
  21. import { createCubicEasingFunc } from './cubicEasing';
  22. import { isLinearGradient, isRadialGradient } from '../svg/helper';
  23. type NumberArray = ArrayLike<number>
  24. type InterpolatableType = string | number | NumberArray | NumberArray[];
  25. interface ParsedColorStop {
  26. color: number[],
  27. offset: number
  28. };
  29. interface ParsedGradientObject {
  30. colorStops: ParsedColorStop[]
  31. x: number
  32. y: number
  33. global: boolean
  34. }
  35. interface ParsedLinearGradientObject extends ParsedGradientObject {
  36. x2: number
  37. y2: number
  38. }
  39. interface ParsedRadialGradientObject extends ParsedGradientObject {
  40. r: number
  41. }
  42. const arraySlice = Array.prototype.slice;
  43. function interpolateNumber(p0: number, p1: number, percent: number): number {
  44. return (p1 - p0) * percent + p0;
  45. }
  46. function interpolate1DArray(
  47. out: NumberArray,
  48. p0: NumberArray,
  49. p1: NumberArray,
  50. percent: number
  51. ) {
  52. // TODO Handling different length TypedArray
  53. const len = p0.length;
  54. for (let i = 0; i < len; i++) {
  55. out[i] = interpolateNumber(p0[i], p1[i], percent);
  56. }
  57. return out;
  58. }
  59. function interpolate2DArray(
  60. out: NumberArray[],
  61. p0: NumberArray[],
  62. p1: NumberArray[],
  63. percent: number
  64. ) {
  65. const len = p0.length;
  66. // TODO differnt length on each item?
  67. const len2 = len && p0[0].length;
  68. for (let i = 0; i < len; i++) {
  69. if (!out[i]) {
  70. out[i] = [];
  71. }
  72. for (let j = 0; j < len2; j++) {
  73. out[i][j] = interpolateNumber(p0[i][j], p1[i][j], percent);
  74. }
  75. }
  76. return out;
  77. }
  78. function add1DArray(
  79. out: NumberArray,
  80. p0: NumberArray,
  81. p1: NumberArray,
  82. sign: 1 | -1
  83. ) {
  84. const len = p0.length;
  85. for (let i = 0; i < len; i++) {
  86. out[i] = p0[i] + p1[i] * sign;
  87. }
  88. return out;
  89. }
  90. function add2DArray(
  91. out: NumberArray[],
  92. p0: NumberArray[],
  93. p1: NumberArray[],
  94. sign: 1 | -1
  95. ) {
  96. const len = p0.length;
  97. const len2 = len && p0[0].length;
  98. for (let i = 0; i < len; i++) {
  99. if (!out[i]) {
  100. out[i] = [];
  101. }
  102. for (let j = 0; j < len2; j++) {
  103. out[i][j] = p0[i][j] + p1[i][j] * sign;
  104. }
  105. }
  106. return out;
  107. }
  108. function fillColorStops(val0: ParsedColorStop[], val1: ParsedColorStop[]) {
  109. const len0 = val0.length;
  110. const len1 = val1.length;
  111. const shorterArr = len0 > len1 ? val1 : val0;
  112. const shorterLen = Math.min(len0, len1);
  113. const last = shorterArr[shorterLen - 1] || { color: [0, 0, 0, 0], offset: 0 };
  114. for (let i = shorterLen; i < Math.max(len0, len1); i++) {
  115. // Use last color stop to fill the shorter array
  116. shorterArr.push({
  117. offset: last.offset,
  118. color: last.color.slice()
  119. });
  120. }
  121. }
  122. // arr0 is source array, arr1 is target array.
  123. // Do some preprocess to avoid error happened when interpolating from arr0 to arr1
  124. function fillArray(
  125. val0: NumberArray | NumberArray[],
  126. val1: NumberArray | NumberArray[],
  127. arrDim: 1 | 2
  128. ) {
  129. // TODO Handling different length TypedArray
  130. let arr0 = val0 as (number | number[])[];
  131. let arr1 = val1 as (number | number[])[];
  132. if (!arr0.push || !arr1.push) {
  133. return;
  134. }
  135. const arr0Len = arr0.length;
  136. const arr1Len = arr1.length;
  137. if (arr0Len !== arr1Len) {
  138. // FIXME Not work for TypedArray
  139. const isPreviousLarger = arr0Len > arr1Len;
  140. if (isPreviousLarger) {
  141. // Cut the previous
  142. arr0.length = arr1Len;
  143. }
  144. else {
  145. // Fill the previous
  146. for (let i = arr0Len; i < arr1Len; i++) {
  147. arr0.push(arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i]));
  148. }
  149. }
  150. }
  151. // Handling NaN value
  152. const len2 = arr0[0] && (arr0[0] as number[]).length;
  153. for (let i = 0; i < arr0.length; i++) {
  154. if (arrDim === 1) {
  155. if (isNaN(arr0[i] as number)) {
  156. arr0[i] = arr1[i];
  157. }
  158. }
  159. else {
  160. for (let j = 0; j < len2; j++) {
  161. if (isNaN((arr0 as number[][])[i][j])) {
  162. (arr0 as number[][])[i][j] = (arr1 as number[][])[i][j];
  163. }
  164. }
  165. }
  166. }
  167. }
  168. export function cloneValue(value: InterpolatableType) {
  169. if (isArrayLike(value)) {
  170. const len = value.length;
  171. if (isArrayLike(value[0])) {
  172. const ret = [];
  173. for (let i = 0; i < len; i++) {
  174. ret.push(arraySlice.call(value[i]));
  175. }
  176. return ret;
  177. }
  178. return arraySlice.call(value);
  179. }
  180. return value;
  181. }
  182. function rgba2String(rgba: number[]): string {
  183. rgba[0] = Math.floor(rgba[0]) || 0;
  184. rgba[1] = Math.floor(rgba[1]) || 0;
  185. rgba[2] = Math.floor(rgba[2]) || 0;
  186. rgba[3] = rgba[3] == null ? 1 : rgba[3];
  187. return 'rgba(' + rgba.join(',') + ')';
  188. }
  189. function guessArrayDim(value: ArrayLike<unknown>): 1 | 2 {
  190. return isArrayLike(value && (value as ArrayLike<unknown>)[0]) ? 2 : 1;
  191. }
  192. const VALUE_TYPE_NUMBER = 0;
  193. const VALUE_TYPE_1D_ARRAY = 1;
  194. const VALUE_TYPE_2D_ARRAY = 2;
  195. const VALUE_TYPE_COLOR = 3;
  196. const VALUE_TYPE_LINEAR_GRADIENT = 4;
  197. const VALUE_TYPE_RADIAL_GRADIENT = 5;
  198. // Other value type that can only use discrete animation.
  199. const VALUE_TYPE_UNKOWN = 6;
  200. type ValueType = 0 | 1 | 2 | 3 | 4 | 5 | 6;
  201. type Keyframe = {
  202. time: number
  203. value: unknown
  204. percent: number
  205. // Raw value for discrete animation.
  206. rawValue: unknown
  207. easing?: AnimationEasing // Raw easing
  208. easingFunc?: (percent: number) => number
  209. additiveValue?: unknown
  210. }
  211. function isGradientValueType(valType: ValueType): valType is 4 | 5 {
  212. return valType === VALUE_TYPE_LINEAR_GRADIENT || valType === VALUE_TYPE_RADIAL_GRADIENT;
  213. }
  214. function isArrayValueType(valType: ValueType): valType is 1 | 2 {
  215. return valType === VALUE_TYPE_1D_ARRAY || valType === VALUE_TYPE_2D_ARRAY;
  216. }
  217. let tmpRgba: number[] = [0, 0, 0, 0];
  218. class Track {
  219. keyframes: Keyframe[] = []
  220. propName: string
  221. valType: ValueType
  222. discrete: boolean = false
  223. _invalid: boolean = false;
  224. private _finished: boolean
  225. private _needsSort: boolean = false
  226. private _additiveTrack: Track
  227. // Temporal storage for interpolated additive value.
  228. private _additiveValue: unknown
  229. // Info for run
  230. /**
  231. * Last frame
  232. */
  233. private _lastFr = 0
  234. /**
  235. * Percent of last frame.
  236. */
  237. private _lastFrP = 0
  238. constructor(propName: string) {
  239. this.propName = propName;
  240. }
  241. isFinished() {
  242. return this._finished;
  243. }
  244. setFinished() {
  245. this._finished = true;
  246. // Also set additive track to finished.
  247. // Make sure the final value stopped on the latest track
  248. if (this._additiveTrack) {
  249. this._additiveTrack.setFinished();
  250. }
  251. }
  252. needsAnimate() {
  253. return this.keyframes.length >= 1;
  254. }
  255. getAdditiveTrack() {
  256. return this._additiveTrack;
  257. }
  258. addKeyframe(time: number, rawValue: unknown, easing?: AnimationEasing) {
  259. this._needsSort = true;
  260. let keyframes = this.keyframes;
  261. let len = keyframes.length;
  262. let discrete = false;
  263. let valType: ValueType = VALUE_TYPE_UNKOWN;
  264. let value = rawValue;
  265. // Handling values only if it's possible to be interpolated.
  266. if (isArrayLike(rawValue)) {
  267. let arrayDim = guessArrayDim(rawValue);
  268. valType = arrayDim;
  269. // Not a number array.
  270. if (arrayDim === 1 && !isNumber(rawValue[0])
  271. || arrayDim === 2 && !isNumber(rawValue[0][0])) {
  272. discrete = true;
  273. }
  274. }
  275. else {
  276. if (isNumber(rawValue) && !eqNaN(rawValue)) {
  277. valType = VALUE_TYPE_NUMBER;
  278. }
  279. else if (isString(rawValue)) {
  280. if (!isNaN(+rawValue)) { // Can be string number like '2'
  281. valType = VALUE_TYPE_NUMBER;
  282. }
  283. else {
  284. const colorArray = color.parse(rawValue);
  285. if (colorArray) {
  286. value = colorArray;
  287. valType = VALUE_TYPE_COLOR;
  288. }
  289. }
  290. }
  291. else if (isGradientObject(rawValue)) {
  292. // TODO Color to gradient or gradient to color.
  293. const parsedGradient = extend({}, value) as unknown as ParsedGradientObject;
  294. parsedGradient.colorStops = map(rawValue.colorStops, colorStop => ({
  295. offset: colorStop.offset,
  296. color: color.parse(colorStop.color)
  297. }));
  298. if (isLinearGradient(rawValue)) {
  299. valType = VALUE_TYPE_LINEAR_GRADIENT;
  300. }
  301. else if (isRadialGradient(rawValue)) {
  302. valType = VALUE_TYPE_RADIAL_GRADIENT;
  303. }
  304. value = parsedGradient;
  305. }
  306. }
  307. if (len === 0) {
  308. // Inference type from the first keyframe.
  309. this.valType = valType;
  310. }
  311. // Not same value type or can't be interpolated.
  312. else if (valType !== this.valType || valType === VALUE_TYPE_UNKOWN) {
  313. discrete = true;
  314. }
  315. this.discrete = this.discrete || discrete;
  316. const kf: Keyframe = {
  317. time,
  318. value,
  319. rawValue,
  320. percent: 0
  321. };
  322. if (easing) {
  323. // Save the raw easing name to be used in css animation output
  324. kf.easing = easing;
  325. kf.easingFunc = isFunction(easing)
  326. ? easing
  327. : easingFuncs[easing] || createCubicEasingFunc(easing);
  328. }
  329. // Not check if value equal here.
  330. keyframes.push(kf);
  331. return kf;
  332. }
  333. prepare(maxTime: number, additiveTrack?: Track) {
  334. let kfs = this.keyframes;
  335. if (this._needsSort) {
  336. // Sort keyframe as ascending
  337. kfs.sort(function (a: Keyframe, b: Keyframe) {
  338. return a.time - b.time;
  339. });
  340. }
  341. const valType = this.valType;
  342. const kfsLen = kfs.length;
  343. const lastKf = kfs[kfsLen - 1];
  344. const isDiscrete = this.discrete;
  345. const isArr = isArrayValueType(valType);
  346. const isGradient = isGradientValueType(valType);
  347. for (let i = 0; i < kfsLen; i++) {
  348. const kf = kfs[i];
  349. const value = kf.value;
  350. const lastValue = lastKf.value;
  351. kf.percent = kf.time / maxTime;
  352. if (!isDiscrete) {
  353. if (isArr && i !== kfsLen - 1) {
  354. // Align array with target frame.
  355. fillArray(value as NumberArray, lastValue as NumberArray, valType);
  356. }
  357. else if (isGradient) {
  358. fillColorStops(
  359. (value as ParsedLinearGradientObject).colorStops,
  360. (lastValue as ParsedLinearGradientObject).colorStops
  361. );
  362. }
  363. }
  364. }
  365. // Only apply additive animaiton on INTERPOLABLE SAME TYPE values.
  366. if (
  367. !isDiscrete
  368. // TODO support gradient
  369. && valType !== VALUE_TYPE_RADIAL_GRADIENT
  370. && additiveTrack
  371. // If two track both will be animated and have same value format.
  372. && this.needsAnimate()
  373. && additiveTrack.needsAnimate()
  374. && valType === additiveTrack.valType
  375. && !additiveTrack._finished
  376. ) {
  377. this._additiveTrack = additiveTrack;
  378. const startValue = kfs[0].value;
  379. // Calculate difference
  380. for (let i = 0; i < kfsLen; i++) {
  381. if (valType === VALUE_TYPE_NUMBER) {
  382. kfs[i].additiveValue = kfs[i].value as number - (startValue as number);
  383. }
  384. else if (valType === VALUE_TYPE_COLOR) {
  385. kfs[i].additiveValue =
  386. add1DArray([], kfs[i].value as NumberArray, startValue as NumberArray, -1);
  387. }
  388. else if (isArrayValueType(valType)) {
  389. kfs[i].additiveValue = valType === VALUE_TYPE_1D_ARRAY
  390. ? add1DArray([], kfs[i].value as NumberArray, startValue as NumberArray, -1)
  391. : add2DArray([], kfs[i].value as NumberArray[], startValue as NumberArray[], -1);
  392. }
  393. }
  394. }
  395. }
  396. step(target: any, percent: number) {
  397. if (this._finished) { // Track may be set to finished.
  398. return;
  399. }
  400. if (this._additiveTrack && this._additiveTrack._finished) {
  401. // Remove additive track if it's finished.
  402. this._additiveTrack = null;
  403. }
  404. const isAdditive = this._additiveTrack != null;
  405. const valueKey = isAdditive ? 'additiveValue' : 'value';
  406. const valType = this.valType;
  407. const keyframes = this.keyframes;
  408. const kfsNum = keyframes.length;
  409. const propName = this.propName;
  410. const isValueColor = valType === VALUE_TYPE_COLOR;
  411. // Find the range keyframes
  412. // kf1-----kf2---------current--------kf3
  413. // find kf2 and kf3 and do interpolation
  414. let frameIdx;
  415. const lastFrame = this._lastFr;
  416. const mathMin = Math.min;
  417. let frame;
  418. let nextFrame;
  419. if (kfsNum === 1) {
  420. frame = nextFrame = keyframes[0];
  421. }
  422. else {
  423. // In the easing function like elasticOut, percent may less than 0
  424. if (percent < 0) {
  425. frameIdx = 0;
  426. }
  427. else if (percent < this._lastFrP) {
  428. // Start from next key
  429. // PENDING start from lastFrame ?
  430. const start = mathMin(lastFrame + 1, kfsNum - 1);
  431. for (frameIdx = start; frameIdx >= 0; frameIdx--) {
  432. if (keyframes[frameIdx].percent <= percent) {
  433. break;
  434. }
  435. }
  436. frameIdx = mathMin(frameIdx, kfsNum - 2);
  437. }
  438. else {
  439. for (frameIdx = lastFrame; frameIdx < kfsNum; frameIdx++) {
  440. if (keyframes[frameIdx].percent > percent) {
  441. break;
  442. }
  443. }
  444. frameIdx = mathMin(frameIdx - 1, kfsNum - 2);
  445. }
  446. nextFrame = keyframes[frameIdx + 1];
  447. frame = keyframes[frameIdx];
  448. }
  449. // Defensive coding.
  450. if (!(frame && nextFrame)) {
  451. return;
  452. }
  453. this._lastFr = frameIdx;
  454. this._lastFrP = percent;
  455. const interval = (nextFrame.percent - frame.percent);
  456. let w = interval === 0 ? 1 : mathMin((percent - frame.percent) / interval, 1);
  457. // Apply different easing of each keyframe.
  458. // Use easing specified in target frame.
  459. if (nextFrame.easingFunc) {
  460. w = nextFrame.easingFunc(w);
  461. }
  462. // If value is arr
  463. let targetArr = isAdditive ? this._additiveValue
  464. : (isValueColor ? tmpRgba : target[propName]);
  465. if ((isArrayValueType(valType) || isValueColor) && !targetArr) {
  466. targetArr = this._additiveValue = [];
  467. }
  468. if (this.discrete) {
  469. // use raw value without parse in discrete animation.
  470. target[propName] = w < 1 ? frame.rawValue : nextFrame.rawValue;
  471. }
  472. else if (isArrayValueType(valType)) {
  473. valType === VALUE_TYPE_1D_ARRAY
  474. ? interpolate1DArray(
  475. targetArr as NumberArray,
  476. frame[valueKey] as NumberArray,
  477. nextFrame[valueKey] as NumberArray,
  478. w
  479. )
  480. : interpolate2DArray(
  481. targetArr as NumberArray[],
  482. frame[valueKey] as NumberArray[],
  483. nextFrame[valueKey] as NumberArray[],
  484. w
  485. );
  486. }
  487. else if (isGradientValueType(valType)) {
  488. const val = frame[valueKey] as ParsedGradientObject;
  489. const nextVal = nextFrame[valueKey] as ParsedGradientObject;
  490. const isLinearGradient = valType === VALUE_TYPE_LINEAR_GRADIENT;
  491. target[propName] = {
  492. type: isLinearGradient ? 'linear' : 'radial',
  493. x: interpolateNumber(val.x, nextVal.x, w),
  494. y: interpolateNumber(val.y, nextVal.y, w),
  495. // TODO performance
  496. colorStops: map(val.colorStops, (colorStop, idx) => {
  497. const nextColorStop = nextVal.colorStops[idx];
  498. return {
  499. offset: interpolateNumber(colorStop.offset, nextColorStop.offset, w),
  500. color: rgba2String(interpolate1DArray(
  501. [] as number[], colorStop.color, nextColorStop.color, w
  502. ) as number[])
  503. };
  504. }),
  505. global: nextVal.global
  506. };
  507. if (isLinearGradient) {
  508. // Linear
  509. target[propName].x2 = interpolateNumber(
  510. (val as ParsedLinearGradientObject).x2, (nextVal as ParsedLinearGradientObject).x2, w
  511. );
  512. target[propName].y2 = interpolateNumber(
  513. (val as ParsedLinearGradientObject).y2, (nextVal as ParsedLinearGradientObject).y2, w
  514. );
  515. }
  516. else {
  517. // Radial
  518. target[propName].r = interpolateNumber(
  519. (val as ParsedRadialGradientObject).r, (nextVal as ParsedRadialGradientObject).r, w
  520. );
  521. }
  522. }
  523. else if (isValueColor) {
  524. interpolate1DArray(
  525. targetArr,
  526. frame[valueKey] as NumberArray,
  527. nextFrame[valueKey] as NumberArray,
  528. w
  529. );
  530. if (!isAdditive) { // Convert to string later:)
  531. target[propName] = rgba2String(targetArr);
  532. }
  533. }
  534. else {
  535. const value = interpolateNumber(frame[valueKey] as number, nextFrame[valueKey] as number, w);
  536. if (isAdditive) {
  537. this._additiveValue = value;
  538. }
  539. else {
  540. target[propName] = value;
  541. }
  542. }
  543. // Add additive to target
  544. if (isAdditive) {
  545. this._addToTarget(target);
  546. }
  547. }
  548. private _addToTarget(target: any) {
  549. const valType = this.valType;
  550. const propName = this.propName;
  551. const additiveValue = this._additiveValue;
  552. if (valType === VALUE_TYPE_NUMBER) {
  553. // Add a difference value based on the change of previous frame.
  554. target[propName] = target[propName] + additiveValue;
  555. }
  556. else if (valType === VALUE_TYPE_COLOR) {
  557. // TODO reduce unnecessary parse
  558. color.parse(target[propName], tmpRgba);
  559. add1DArray(tmpRgba, tmpRgba, additiveValue as NumberArray, 1);
  560. target[propName] = rgba2String(tmpRgba);
  561. }
  562. else if (valType === VALUE_TYPE_1D_ARRAY) {
  563. add1DArray(target[propName], target[propName], additiveValue as NumberArray, 1);
  564. }
  565. else if (valType === VALUE_TYPE_2D_ARRAY) {
  566. add2DArray(target[propName], target[propName], additiveValue as NumberArray[], 1);
  567. }
  568. }
  569. }
  570. type DoneCallback = () => void;
  571. type AbortCallback = () => void;
  572. export type OnframeCallback<T> = (target: T, percent: number) => void;
  573. export type AnimationPropGetter<T> = (target: T, key: string) => InterpolatableType;
  574. export type AnimationPropSetter<T> = (target: T, key: string, value: InterpolatableType) => void;
  575. export default class Animator<T> {
  576. animation?: Animation
  577. targetName?: string
  578. scope?: string
  579. __fromStateTransition?: string
  580. private _tracks: Dictionary<Track> = {}
  581. private _trackKeys: string[] = []
  582. private _target: T
  583. private _loop: boolean
  584. private _delay: number
  585. private _maxTime = 0
  586. /**
  587. * If force run regardless of empty tracks when duration is set.
  588. */
  589. private _force: boolean;
  590. /**
  591. * If animator is paused
  592. * @default false
  593. */
  594. private _paused: boolean
  595. // 0: Not started
  596. // 1: Invoked started
  597. // 2: Has been run for at least one frame.
  598. private _started = 0
  599. /**
  600. * If allow discrete animation
  601. * @default false
  602. */
  603. private _allowDiscrete: boolean
  604. private _additiveAnimators: Animator<any>[]
  605. private _doneCbs: DoneCallback[]
  606. private _onframeCbs: OnframeCallback<T>[]
  607. private _abortedCbs: AbortCallback[]
  608. private _clip: Clip = null
  609. constructor(
  610. target: T,
  611. loop: boolean,
  612. allowDiscreteAnimation?: boolean, // If doing discrete animation on the values can't be interpolated
  613. additiveTo?: Animator<any>[]
  614. ) {
  615. this._target = target;
  616. this._loop = loop;
  617. if (loop && additiveTo) {
  618. logError('Can\' use additive animation on looped animation.');
  619. return;
  620. }
  621. this._additiveAnimators = additiveTo;
  622. this._allowDiscrete = allowDiscreteAnimation;
  623. }
  624. getMaxTime() {
  625. return this._maxTime;
  626. }
  627. getDelay() {
  628. return this._delay;
  629. }
  630. getLoop() {
  631. return this._loop;
  632. }
  633. getTarget() {
  634. return this._target;
  635. }
  636. /**
  637. * Target can be changed during animation
  638. * For example if style is changed during state change.
  639. * We need to change target to the new style object.
  640. */
  641. changeTarget(target: T) {
  642. this._target = target;
  643. }
  644. /**
  645. * Set Animation keyframe
  646. * @param time time of keyframe in ms
  647. * @param props key-value props of keyframe.
  648. * @param easing
  649. */
  650. when(time: number, props: Dictionary<any>, easing?: AnimationEasing) {
  651. return this.whenWithKeys(time, props, keys(props) as string[], easing);
  652. }
  653. // Fast path for add keyframes of aniamteTo
  654. whenWithKeys(time: number, props: Dictionary<any>, propNames: string[], easing?: AnimationEasing) {
  655. const tracks = this._tracks;
  656. for (let i = 0; i < propNames.length; i++) {
  657. const propName = propNames[i];
  658. let track = tracks[propName];
  659. if (!track) {
  660. track = tracks[propName] = new Track(propName);
  661. let initialValue;
  662. const additiveTrack = this._getAdditiveTrack(propName);
  663. if (additiveTrack) {
  664. const addtiveTrackKfs = additiveTrack.keyframes;
  665. const lastFinalKf = addtiveTrackKfs[addtiveTrackKfs.length - 1];
  666. // Use the last state of additived animator.
  667. initialValue = lastFinalKf && lastFinalKf.value;
  668. if (additiveTrack.valType === VALUE_TYPE_COLOR && initialValue) {
  669. // Convert to rgba string
  670. initialValue = rgba2String(initialValue as number[]);
  671. }
  672. }
  673. else {
  674. initialValue = (this._target as any)[propName];
  675. }
  676. // Invalid value
  677. if (initialValue == null) {
  678. // zrLog('Invalid property ' + propName);
  679. continue;
  680. }
  681. // If time is <= 0
  682. // Then props is given initialize value
  683. // Note: initial percent can be negative, which means the initial value is before the animation start.
  684. // Else
  685. // Initialize value from current prop value
  686. if (time > 0) {
  687. track.addKeyframe(0, cloneValue(initialValue), easing);
  688. }
  689. this._trackKeys.push(propName);
  690. }
  691. track.addKeyframe(time, cloneValue(props[propName]), easing);
  692. }
  693. this._maxTime = Math.max(this._maxTime, time);
  694. return this;
  695. }
  696. pause() {
  697. this._clip.pause();
  698. this._paused = true;
  699. }
  700. resume() {
  701. this._clip.resume();
  702. this._paused = false;
  703. }
  704. isPaused(): boolean {
  705. return !!this._paused;
  706. }
  707. /**
  708. * Set duration of animator.
  709. * Will run this duration regardless the track max time or if trackes exits.
  710. * @param duration
  711. * @returns
  712. */
  713. duration(duration: number) {
  714. this._maxTime = duration;
  715. this._force = true;
  716. return this;
  717. }
  718. private _doneCallback() {
  719. this._setTracksFinished();
  720. // Clear clip
  721. this._clip = null;
  722. const doneList = this._doneCbs;
  723. if (doneList) {
  724. const len = doneList.length;
  725. for (let i = 0; i < len; i++) {
  726. doneList[i].call(this);
  727. }
  728. }
  729. }
  730. private _abortedCallback() {
  731. this._setTracksFinished();
  732. const animation = this.animation;
  733. const abortedList = this._abortedCbs;
  734. if (animation) {
  735. animation.removeClip(this._clip);
  736. }
  737. this._clip = null;
  738. if (abortedList) {
  739. for (let i = 0; i < abortedList.length; i++) {
  740. abortedList[i].call(this);
  741. }
  742. }
  743. }
  744. private _setTracksFinished() {
  745. const tracks = this._tracks;
  746. const tracksKeys = this._trackKeys;
  747. for (let i = 0; i < tracksKeys.length; i++) {
  748. tracks[tracksKeys[i]].setFinished();
  749. }
  750. }
  751. private _getAdditiveTrack(trackName: string): Track {
  752. let additiveTrack;
  753. const additiveAnimators = this._additiveAnimators;
  754. if (additiveAnimators) {
  755. for (let i = 0; i < additiveAnimators.length; i++) {
  756. const track = additiveAnimators[i].getTrack(trackName);
  757. if (track) {
  758. // Use the track of latest animator.
  759. additiveTrack = track;
  760. }
  761. }
  762. }
  763. return additiveTrack;
  764. }
  765. /**
  766. * Start the animation
  767. * @param easing
  768. * @return
  769. */
  770. start(easing?: AnimationEasing) {
  771. if (this._started > 0) {
  772. return;
  773. }
  774. this._started = 1;
  775. const self = this;
  776. const tracks: Track[] = [];
  777. const maxTime = this._maxTime || 0;
  778. for (let i = 0; i < this._trackKeys.length; i++) {
  779. const propName = this._trackKeys[i];
  780. const track = this._tracks[propName];
  781. const additiveTrack = this._getAdditiveTrack(propName);
  782. const kfs = track.keyframes;
  783. const kfsNum = kfs.length;
  784. track.prepare(maxTime, additiveTrack);
  785. if (track.needsAnimate()) {
  786. // Set value directly if discrete animation is not allowed.
  787. if (!this._allowDiscrete && track.discrete) {
  788. const lastKf = kfs[kfsNum - 1];
  789. // Set final value.
  790. if (lastKf) {
  791. // use raw value without parse.
  792. (self._target as any)[track.propName] = lastKf.rawValue;
  793. }
  794. track.setFinished();
  795. }
  796. else {
  797. tracks.push(track);
  798. }
  799. }
  800. }
  801. // Add during callback on the last clip
  802. if (tracks.length || this._force) {
  803. const clip = new Clip({
  804. life: maxTime,
  805. loop: this._loop,
  806. delay: this._delay || 0,
  807. onframe(percent: number) {
  808. self._started = 2;
  809. // Remove additived animator if it's finished.
  810. // For the purpose of memory effeciency.
  811. const additiveAnimators = self._additiveAnimators;
  812. if (additiveAnimators) {
  813. let stillHasAdditiveAnimator = false;
  814. for (let i = 0; i < additiveAnimators.length; i++) {
  815. if (additiveAnimators[i]._clip) {
  816. stillHasAdditiveAnimator = true;
  817. break;
  818. }
  819. }
  820. if (!stillHasAdditiveAnimator) {
  821. self._additiveAnimators = null;
  822. }
  823. }
  824. for (let i = 0; i < tracks.length; i++) {
  825. // NOTE: don't cache target outside.
  826. // Because target may be changed.
  827. tracks[i].step(self._target, percent);
  828. }
  829. const onframeList = self._onframeCbs;
  830. if (onframeList) {
  831. for (let i = 0; i < onframeList.length; i++) {
  832. onframeList[i](self._target, percent);
  833. }
  834. }
  835. },
  836. ondestroy() {
  837. self._doneCallback();
  838. }
  839. });
  840. this._clip = clip;
  841. if (this.animation) {
  842. this.animation.addClip(clip);
  843. }
  844. if (easing) {
  845. clip.setEasing(easing);
  846. }
  847. }
  848. else {
  849. // This optimization will help the case that in the upper application
  850. // the view may be refreshed frequently, where animation will be
  851. // called repeatly but nothing changed.
  852. this._doneCallback();
  853. }
  854. return this;
  855. }
  856. /**
  857. * Stop animation
  858. * @param {boolean} forwardToLast If move to last frame before stop
  859. */
  860. stop(forwardToLast?: boolean) {
  861. if (!this._clip) {
  862. return;
  863. }
  864. const clip = this._clip;
  865. if (forwardToLast) {
  866. // Move to last frame before stop
  867. clip.onframe(1);
  868. }
  869. this._abortedCallback();
  870. }
  871. /**
  872. * Set when animation delay starts
  873. * @param time 单位ms
  874. */
  875. delay(time: number) {
  876. this._delay = time;
  877. return this;
  878. }
  879. /**
  880. * 添加动画每一帧的回调函数
  881. * @param callback
  882. */
  883. during(cb: OnframeCallback<T>) {
  884. if (cb) {
  885. if (!this._onframeCbs) {
  886. this._onframeCbs = [];
  887. }
  888. this._onframeCbs.push(cb);
  889. }
  890. return this;
  891. }
  892. /**
  893. * Add callback for animation end
  894. * @param cb
  895. */
  896. done(cb: DoneCallback) {
  897. if (cb) {
  898. if (!this._doneCbs) {
  899. this._doneCbs = [];
  900. }
  901. this._doneCbs.push(cb);
  902. }
  903. return this;
  904. }
  905. aborted(cb: AbortCallback) {
  906. if (cb) {
  907. if (!this._abortedCbs) {
  908. this._abortedCbs = [];
  909. }
  910. this._abortedCbs.push(cb);
  911. }
  912. return this;
  913. }
  914. getClip() {
  915. return this._clip;
  916. }
  917. getTrack(propName: string) {
  918. return this._tracks[propName];
  919. }
  920. getTracks() {
  921. return map(this._trackKeys, key => this._tracks[key]);
  922. }
  923. /**
  924. * Return true if animator is not available anymore.
  925. */
  926. stopTracks(propNames: string[], forwardToLast?: boolean): boolean {
  927. if (!propNames.length || !this._clip) {
  928. return true;
  929. }
  930. const tracks = this._tracks;
  931. const tracksKeys = this._trackKeys;
  932. for (let i = 0; i < propNames.length; i++) {
  933. const track = tracks[propNames[i]];
  934. if (track && !track.isFinished()) {
  935. if (forwardToLast) {
  936. track.step(this._target, 1);
  937. }
  938. // If the track has not been run for at least one frame.
  939. // The property may be stayed at the final state. when setToFinal is set true.
  940. // For example:
  941. // Animate x from 0 to 100, then animate to 150 immediately.
  942. // We want the x is translated from 0 to 150, not 100 to 150.
  943. else if (this._started === 1) {
  944. track.step(this._target, 0);
  945. }
  946. // Set track to finished
  947. track.setFinished();
  948. }
  949. }
  950. let allAborted = true;
  951. for (let i = 0; i < tracksKeys.length; i++) {
  952. if (!tracks[tracksKeys[i]].isFinished()) {
  953. allAborted = false;
  954. break;
  955. }
  956. }
  957. // Remove clip if all tracks has been aborted.
  958. if (allAborted) {
  959. this._abortedCallback();
  960. }
  961. return allAborted;
  962. }
  963. /**
  964. * Save values of final state to target.
  965. * It is mainly used in state mangement. When state is switching during animation.
  966. * We need to save final state of animation to the normal state. Not interpolated value.
  967. *
  968. * @param target
  969. * @param trackKeys
  970. * @param firstOrLast If save first frame or last frame
  971. */
  972. saveTo(
  973. target: T,
  974. trackKeys?: readonly string[],
  975. firstOrLast?: boolean
  976. ) {
  977. if (!target) { // DO nothing if target is not given.
  978. return;
  979. }
  980. trackKeys = trackKeys || this._trackKeys;
  981. for (let i = 0; i < trackKeys.length; i++) {
  982. const propName = trackKeys[i];
  983. const track = this._tracks[propName];
  984. if (!track || track.isFinished()) { // Ignore finished track.
  985. continue;
  986. }
  987. const kfs = track.keyframes;
  988. const kf = kfs[firstOrLast ? 0 : kfs.length - 1];
  989. if (kf) {
  990. // TODO CLONE?
  991. // Use raw value without parse.
  992. (target as any)[propName] = cloneValue(kf.rawValue as any);
  993. }
  994. }
  995. }
  996. // Change final value after animator has been started.
  997. // NOTE: Be careful to use it.
  998. __changeFinalValue(finalProps: Dictionary<any>, trackKeys?: readonly string[]) {
  999. trackKeys = trackKeys || keys(finalProps);
  1000. for (let i = 0; i < trackKeys.length; i++) {
  1001. const propName = trackKeys[i];
  1002. const track = this._tracks[propName];
  1003. if (!track) {
  1004. continue;
  1005. }
  1006. const kfs = track.keyframes;
  1007. if (kfs.length > 1) {
  1008. // Remove the original last kf and add again.
  1009. const lastKf = kfs.pop();
  1010. track.addKeyframe(lastKf.time, finalProps[propName]);
  1011. // Prepare again.
  1012. track.prepare(this._maxTime, track.getAdditiveTrack());
  1013. }
  1014. }
  1015. }
  1016. }
  1017. export type AnimatorTrack = Track;