Animation.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /* eslint-disable @typescript-eslint/ban-types */
  2. /* eslint-disable react/destructuring-assignment */
  3. import React, { PureComponent, isValidElement } from 'react';
  4. import PropTypes from 'prop-types';
  5. import { Animation as SemiAnimation, events } from '@douyinfe/semi-animation';
  6. import noop from './utils/noop';
  7. export interface AnimationProps {
  8. onStart?: Function;
  9. onFrame?: Function;
  10. onPause?: Function;
  11. onResume?: Function;
  12. onStop?: Function;
  13. onRest?: Function;
  14. children?: React.ReactNode | ((AnimationChildProps?: any) => React.ReactNode);
  15. from?: Record<string, any>;
  16. to?: Record<string, any>;
  17. reverse?: boolean;
  18. reset?: boolean;
  19. force?: boolean;
  20. config?: Record<string, any>;
  21. autoStart?: boolean;
  22. forwardInstance?: (value: any) => void;
  23. immediate?: boolean
  24. }
  25. export default class Animation extends PureComponent<AnimationProps> {
  26. static propTypes = {
  27. onStart: PropTypes.func,
  28. onFrame: PropTypes.func,
  29. onPause: PropTypes.func,
  30. onResume: PropTypes.func,
  31. onStop: PropTypes.func,
  32. onRest: PropTypes.func,
  33. children: PropTypes.any,
  34. from: PropTypes.object,
  35. to: PropTypes.object,
  36. reverse: PropTypes.bool,
  37. reset: PropTypes.bool,
  38. force: PropTypes.bool,
  39. config: PropTypes.object,
  40. autoStart: PropTypes.bool,
  41. forwardInstance: PropTypes.func,
  42. immediate: PropTypes.bool,
  43. };
  44. static defaultProps = {
  45. autoStart: true,
  46. force: false,
  47. onStart: noop,
  48. onFrame: noop,
  49. onPause: noop,
  50. onResume: noop,
  51. onStop: noop,
  52. onRest: noop,
  53. };
  54. _mounted: boolean;
  55. _destroyed: boolean;
  56. animation: any;
  57. reverse: () => void;
  58. destroy: () => void;
  59. reset: () => void;
  60. resume: () => void;
  61. end: () => void;
  62. stop: () => void;
  63. pause: () => void;
  64. start: () => void;
  65. constructor(props = {}) {
  66. super(props);
  67. this.state = {
  68. currentStyle: {},
  69. };
  70. this._mounted = false;
  71. this._destroyed = false;
  72. this.initAnimation();
  73. this.bindEvents();
  74. }
  75. startOrNot() {
  76. throw new Error('Method not implemented.');
  77. }
  78. componentDidMount() {
  79. this._mounted = true;
  80. const { forwardInstance } = this.props;
  81. if (this.animation === null) {
  82. // didmount/willUnmount may be called twice when React.StrictMode is true in React 18, we need to ensure that this.animation is correct
  83. this.initAnimation();
  84. this.bindEvents();
  85. }
  86. if (typeof forwardInstance === 'function') {
  87. forwardInstance(this.animation);
  88. }
  89. this.startOrNot();
  90. }
  91. componentWillUnmount() {
  92. this._mounted = false;
  93. if (this.animation) {
  94. this.animation.destroy();
  95. this.animation = null;
  96. }
  97. }
  98. componentDidUpdate(prevProps: AnimationProps = {}) {
  99. if (this.props.reset) {
  100. if (this.props.from !== prevProps.from || this.props.to !== prevProps.to) {
  101. this.destroy();
  102. this.initAnimation();
  103. this.startOrNot();
  104. }
  105. }
  106. if (this.props.force) {
  107. if (this.props.to !== prevProps.to) {
  108. this.initAnimation({
  109. ...this.props,
  110. from: prevProps.to,
  111. });
  112. this.startOrNot();
  113. }
  114. }
  115. }
  116. initAnimation = (props?: AnimationProps) => {
  117. // eslint-disable-next-line eqeqeq
  118. props = props == null ? this.props : props;
  119. // eslint-disable-next-line prefer-const
  120. let { from, to, config, reverse } = props;
  121. if (reverse) {
  122. [from, to] = [to, from];
  123. }
  124. this.animation = new SemiAnimation(
  125. {
  126. from: { ...from },
  127. to: { ...to },
  128. },
  129. {
  130. ...config,
  131. }
  132. );
  133. events.forEach((event: string) => {
  134. const propName = `on${event[0].toUpperCase() + event.slice(1)}`;
  135. // eslint-disable-next-line @typescript-eslint/no-shadow
  136. this.animation.on(event, (props: any) => {
  137. // avoid memory leak
  138. if (this._mounted && !this._destroyed) {
  139. this.setState({
  140. currentStyle: { ...props },
  141. });
  142. this.props[propName](props);
  143. }
  144. });
  145. });
  146. this._destroyed = false;
  147. };
  148. bindEvents = () => {
  149. this.startOrNot = () => {
  150. const { immediate, autoStart } = this.props;
  151. if (immediate) {
  152. this.end();
  153. } else if (autoStart) {
  154. this.start();
  155. }
  156. };
  157. this.start = () => {
  158. this.animation && this.animation.start();
  159. };
  160. this.pause = () => {
  161. this.animation && this.animation.pause();
  162. };
  163. this.stop = () => {
  164. this.animation && this.animation.stop();
  165. };
  166. this.end = () => {
  167. this.animation && this.animation.end();
  168. };
  169. this.resume = () => {
  170. this.animation && this.animation.resume();
  171. };
  172. this.reset = () => {
  173. if (this.animation) {
  174. this.animation.reset();
  175. this.startOrNot();
  176. }
  177. };
  178. this.reverse = () => {
  179. if (this.animation) {
  180. this.animation.reverse();
  181. this.startOrNot();
  182. }
  183. };
  184. this.destroy = () => {
  185. this._destroyed = true;
  186. this.animation && this.animation.destroy();
  187. };
  188. };
  189. render() {
  190. const { children } = this.props;
  191. if (typeof children === 'function') {
  192. return children(this.animation.getCurrentStates());
  193. } else if (isValidElement(children)) {
  194. return children;
  195. } else {
  196. return null;
  197. }
  198. }
  199. }