fa14c05bf1b928e0b9df432843041f101ed85b733e344780d4f2b4d450d52aca19de1aaa8a68dbe6bbeee82795f6de6ce956622e9180a581174426bd688be5 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /**
  2. * Animation main class, dispatch and manage all animation controllers
  3. *
  4. */
  5. // TODO Additive animation
  6. // http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/
  7. // https://developer.apple.com/videos/wwdc2014/#236
  8. import Eventful from '../core/Eventful';
  9. import requestAnimationFrame from './requestAnimationFrame';
  10. import Animator from './Animator';
  11. import Clip from './Clip';
  12. export function getTime() {
  13. return new Date().getTime();
  14. }
  15. interface Stage {
  16. update?: () => void
  17. }
  18. interface AnimationOption {
  19. stage?: Stage
  20. }
  21. /**
  22. * @example
  23. * const animation = new Animation();
  24. * const obj = {
  25. * x: 100,
  26. * y: 100
  27. * };
  28. * animation.animate(node.position)
  29. * .when(1000, {
  30. * x: 500,
  31. * y: 500
  32. * })
  33. * .when(2000, {
  34. * x: 100,
  35. * y: 100
  36. * })
  37. * .start();
  38. */
  39. export default class Animation extends Eventful {
  40. stage: Stage
  41. // Use linked list to store clip
  42. private _head: Clip
  43. private _tail: Clip
  44. private _running = false
  45. private _time = 0
  46. private _pausedTime = 0
  47. private _pauseStart = 0
  48. private _paused = false;
  49. constructor(opts?: AnimationOption) {
  50. super();
  51. opts = opts || {};
  52. this.stage = opts.stage || {};
  53. }
  54. /**
  55. * Add clip
  56. */
  57. addClip(clip: Clip) {
  58. if (clip.animation) {
  59. // Clip has been added
  60. this.removeClip(clip);
  61. }
  62. if (!this._head) {
  63. this._head = this._tail = clip;
  64. }
  65. else {
  66. this._tail.next = clip;
  67. clip.prev = this._tail;
  68. clip.next = null;
  69. this._tail = clip;
  70. }
  71. clip.animation = this;
  72. }
  73. /**
  74. * Add animator
  75. */
  76. addAnimator(animator: Animator<any>) {
  77. animator.animation = this;
  78. const clip = animator.getClip();
  79. if (clip) {
  80. this.addClip(clip);
  81. }
  82. }
  83. /**
  84. * Delete animation clip
  85. */
  86. removeClip(clip: Clip) {
  87. if (!clip.animation) {
  88. return;
  89. }
  90. const prev = clip.prev;
  91. const next = clip.next;
  92. if (prev) {
  93. prev.next = next;
  94. }
  95. else {
  96. // Is head
  97. this._head = next;
  98. }
  99. if (next) {
  100. next.prev = prev;
  101. }
  102. else {
  103. // Is tail
  104. this._tail = prev;
  105. }
  106. clip.next = clip.prev = clip.animation = null;
  107. }
  108. /**
  109. * Delete animation clip
  110. */
  111. removeAnimator(animator: Animator<any>) {
  112. const clip = animator.getClip();
  113. if (clip) {
  114. this.removeClip(clip);
  115. }
  116. animator.animation = null;
  117. }
  118. update(notTriggerFrameAndStageUpdate?: boolean) {
  119. const time = getTime() - this._pausedTime;
  120. const delta = time - this._time;
  121. let clip = this._head;
  122. while (clip) {
  123. // Save the nextClip before step.
  124. // So the loop will not been affected if the clip is removed in the callback
  125. const nextClip = clip.next;
  126. let finished = clip.step(time, delta);
  127. if (finished) {
  128. clip.ondestroy();
  129. this.removeClip(clip);
  130. clip = nextClip;
  131. }
  132. else {
  133. clip = nextClip;
  134. }
  135. }
  136. this._time = time;
  137. if (!notTriggerFrameAndStageUpdate) {
  138. // 'frame' should be triggered before stage, because upper application
  139. // depends on the sequence (e.g., echarts-stream and finish
  140. // event judge)
  141. this.trigger('frame', delta);
  142. this.stage.update && this.stage.update();
  143. }
  144. }
  145. _startLoop() {
  146. const self = this;
  147. this._running = true;
  148. function step() {
  149. if (self._running) {
  150. requestAnimationFrame(step);
  151. !self._paused && self.update();
  152. }
  153. }
  154. requestAnimationFrame(step);
  155. }
  156. /**
  157. * Start animation.
  158. */
  159. start() {
  160. if (this._running) {
  161. return;
  162. }
  163. this._time = getTime();
  164. this._pausedTime = 0;
  165. this._startLoop();
  166. }
  167. /**
  168. * Stop animation.
  169. */
  170. stop() {
  171. this._running = false;
  172. }
  173. /**
  174. * Pause animation.
  175. */
  176. pause() {
  177. if (!this._paused) {
  178. this._pauseStart = getTime();
  179. this._paused = true;
  180. }
  181. }
  182. /**
  183. * Resume animation.
  184. */
  185. resume() {
  186. if (this._paused) {
  187. this._pausedTime += getTime() - this._pauseStart;
  188. this._paused = false;
  189. }
  190. }
  191. /**
  192. * Clear animation.
  193. */
  194. clear() {
  195. let clip = this._head;
  196. while (clip) {
  197. let nextClip = clip.next;
  198. clip.prev = clip.next = clip.animation = null;
  199. clip = nextClip;
  200. }
  201. this._head = this._tail = null;
  202. }
  203. /**
  204. * Whether animation finished.
  205. */
  206. isFinished() {
  207. return this._head == null;
  208. }
  209. /**
  210. * Creat animator for a target, whose props can be animated.
  211. */
  212. // TODO Gap
  213. animate<T>(target: T, options: {
  214. loop?: boolean // Whether loop animation
  215. }) {
  216. options = options || {};
  217. // Start animation loop
  218. this.start();
  219. const animator = new Animator(
  220. target,
  221. options.loop
  222. );
  223. this.addAnimator(animator);
  224. return animator;
  225. }
  226. }