123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- /* eslint-disable @typescript-eslint/ban-types */
- /* eslint-disable react/destructuring-assignment */
- import React, { PureComponent, isValidElement } from 'react';
- import PropTypes from 'prop-types';
- import { Animation as SemiAnimation, events } from '@douyinfe/semi-animation';
- import noop from './utils/noop';
- export interface AnimationProps {
- onStart?: Function;
- onFrame?: Function;
- onPause?: Function;
- onResume?: Function;
- onStop?: Function;
- onRest?: Function;
- children?: React.ReactNode | ((AnimationChildProps?: any) => React.ReactNode);
- from?: Record<string, any>;
- to?: Record<string, any>;
- reverse?: boolean;
- reset?: boolean;
- force?: boolean;
- config?: Record<string, any>;
- autoStart?: boolean;
- forwardInstance?: (value: any) => void;
- immediate?: boolean
- }
- export default class Animation extends PureComponent<AnimationProps> {
- static propTypes = {
- onStart: PropTypes.func,
- onFrame: PropTypes.func,
- onPause: PropTypes.func,
- onResume: PropTypes.func,
- onStop: PropTypes.func,
- onRest: PropTypes.func,
- children: PropTypes.any,
- from: PropTypes.object,
- to: PropTypes.object,
- reverse: PropTypes.bool,
- reset: PropTypes.bool,
- force: PropTypes.bool,
- config: PropTypes.object,
- autoStart: PropTypes.bool,
- forwardInstance: PropTypes.func,
- immediate: PropTypes.bool,
- };
- static defaultProps = {
- autoStart: true,
- force: false,
- onStart: noop,
- onFrame: noop,
- onPause: noop,
- onResume: noop,
- onStop: noop,
- onRest: noop,
- };
- _mounted: boolean;
- _destroyed: boolean;
- animation: any;
- reverse: () => void;
- destroy: () => void;
- reset: () => void;
- resume: () => void;
- end: () => void;
- stop: () => void;
- pause: () => void;
- start: () => void;
- constructor(props = {}) {
- super(props);
- this.state = {
- currentStyle: {},
- };
- this._mounted = false;
- this._destroyed = false;
- this.initAnimation();
- this.bindEvents();
- }
- startOrNot() {
- throw new Error('Method not implemented.');
- }
- componentDidMount() {
- this._mounted = true;
- const { forwardInstance } = this.props;
- if (this.animation === null) {
- // didmount/willUnmount may be called twice when React.StrictMode is true in React 18, we need to ensure that this.animation is correct
- this.initAnimation();
- this.bindEvents();
- }
- if (typeof forwardInstance === 'function') {
- forwardInstance(this.animation);
- }
- this.startOrNot();
- }
- componentWillUnmount() {
- this._mounted = false;
- if (this.animation) {
- this.animation.destroy();
- this.animation = null;
- }
- }
- componentDidUpdate(prevProps: AnimationProps = {}) {
- if (this.props.reset) {
- if (this.props.from !== prevProps.from || this.props.to !== prevProps.to) {
- this.destroy();
- this.initAnimation();
- this.startOrNot();
- }
- }
- if (this.props.force) {
- if (this.props.to !== prevProps.to) {
- this.initAnimation({
- ...this.props,
- from: prevProps.to,
- });
- this.startOrNot();
- }
- }
- }
- initAnimation = (props?: AnimationProps) => {
- // eslint-disable-next-line eqeqeq
- props = props == null ? this.props : props;
- // eslint-disable-next-line prefer-const
- let { from, to, config, reverse } = props;
- if (reverse) {
- [from, to] = [to, from];
- }
- this.animation = new SemiAnimation(
- {
- from: { ...from },
- to: { ...to },
- },
- {
- ...config,
- }
- );
- events.forEach((event: string) => {
- const propName = `on${event[0].toUpperCase() + event.slice(1)}`;
- // eslint-disable-next-line @typescript-eslint/no-shadow
- this.animation.on(event, (props: any) => {
- // avoid memory leak
- if (this._mounted && !this._destroyed) {
- this.setState({
- currentStyle: { ...props },
- });
- this.props[propName](props);
- }
- });
- });
- this._destroyed = false;
- };
- bindEvents = () => {
- this.startOrNot = () => {
- const { immediate, autoStart } = this.props;
- if (immediate) {
- this.end();
- } else if (autoStart) {
- this.start();
- }
- };
- this.start = () => {
- this.animation && this.animation.start();
- };
- this.pause = () => {
- this.animation && this.animation.pause();
- };
- this.stop = () => {
- this.animation && this.animation.stop();
- };
- this.end = () => {
- this.animation && this.animation.end();
- };
- this.resume = () => {
- this.animation && this.animation.resume();
- };
- this.reset = () => {
- if (this.animation) {
- this.animation.reset();
- this.startOrNot();
- }
- };
- this.reverse = () => {
- if (this.animation) {
- this.animation.reverse();
- this.startOrNot();
- }
- };
- this.destroy = () => {
- this._destroyed = true;
- this.animation && this.animation.destroy();
- };
- };
- render() {
- const { children } = this.props;
- if (typeof children === 'function') {
- return children(this.animation.getCurrentStates());
- } else if (isValidElement(children)) {
- return children;
- } else {
- return null;
- }
- }
- }
|