欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

在React中写一个Animation组件,为组件进入和离开加上动画/过度

程序员文章站 2022-03-26 12:21:05
问题 在单页面应用中,我们经常需要给路由的切换或者元素的挂载和卸载加上过渡效果,为这么一个小功能引入第三方框架,实在有点小纠结。不如自己封装。 思路 原理 以进入时 ,退出时 为例 元素挂载时 1. 挂载元素dom 2. 设置动画 元素卸载时 1. 设置动画 2. 动画结束后卸载dom 组件设计 为 ......

问题

在单页面应用中,我们经常需要给路由的切换或者元素的挂载和卸载加上过渡效果,为这么一个小功能引入第三方框架,实在有点小纠结。不如自己封装。

思路

原理

以进入时opacity: 0 --> opacity: 1 ,退出时opacity: 0 --> opacity: 1为例

元素挂载时

  1. 挂载元素dom
  2. 设置动画opacity: 0 --> opacity: 1

元素卸载时

  1. 设置动画opacity: 0 --> opacity: 1
  2. 动画结束后卸载dom

组件设计

为了使得组件简单易用、低耦合,我们期望如下方式来调用组件:

属性名 类型 描述
isshow boolean 子元素显示或隐藏控制
name string 指定一个name,动画进入退出时的动画

app.jsx里调用组件:

通过改变isshow的值来指定是否显示

// app.jsx
// 其他代码省略
import './app.css';

<animation isshow={isshow} name='demo'>
    <div class='demo'>
        demo
    </div>
</animation>
// 通过改变isshow的值来指定是否显示

app.css里指定进入离开效果:

// 基础样式
.demo {
    width: 200px;
    height: 200px;
    background-color: red;
}

// 定义进出入动画
.demo-showing {
    animation: show 0.5s forwards;
}
.demo-fading {
    animation: fade 0.5s forwards;
}

// 定义动画fade与show
@keyframes show {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

@keyframes fade {
    from {
        opacity: 1;
    }
    to {
        opacity: 0;
    }
}

根据思路写代码

// animation.jsx
import { purecomponent } from 'react';
import './index.css';

class animation extends purecomponent {
    constructor(props) {
        super(props);
        this.state = {
            isinnershow: false,
            animationclass: '',
        };
    }

    componentwillreceiveprops(props) {
        const { isshow } = props;
        if (isshow) {
            // 显示
            this.show().then(() => {
                this.doshowanimation();
            });
        } else {
            // 隐藏
            this.dofadeanimation();
        }
    }

    handleanimationend() {
        const isfading = this.state.animationclass === this.classname('fading');
        if (isfading) {
            this.hide();
        }
    }

    show() {
        return new promise(resolve => {
            this.setstate(
                {
                    isinnershow: true,
                },
                () => {
                    resolve();
                }
            );
        });
    }

    hide() {
        this.setstate({
            isinnershow: false,
        });
    }

    doshowanimation() {
        this.setstate({
            animationclass: this.classname('showing'),
        });
    }

    dofadeanimation() {
        this.setstate({
            animationclass: this.classname('fading'),
        });
    }

    /**
     * 获取classname
     * @param {string} inner 'showing' | 'fading'
     */
    classname(inner) {
        const { name } = this.props;
        if (!name) throw new error('animation name must be assigned');
        return `${name}-${inner}`;
    }

    render() {
        let { children } = this.props;
        children = react.children.only(children);
        const { isinnershow, animationclass } = this.state;
        const element = {
            ...children,
            props: {
                ...children.props,
                classname: `${children.props.classname} ${animationclass}`,
                onanimationend: this.handleanimationend.bind(this),
            },
        };
        return isinnershow && element;
    }
}

export default animation;

demo示例