React:实现一个带有loading效果的按钮组件

栏目: IOS · Android · 发布时间: 6年前

导语: 在react中如何实现一个带有loading效果的按钮组件呢?

在业务经常需要点击按钮去请求接口,在接口还未返回结果前,需要给用户一个等待的感觉,同时锁住按钮,防止产生二次点击!

我们实现带有loading效果的按钮组件,主要是实现以下的几个功能:

  • 有菊花或者圈圈的loading图标,且loading颜色与字体颜色相同;

  • loading的过程中,点击无效;

1. 按钮的结构

针对第1个功能,我最开始是把接口返回结果之前的loading集成到了按钮中,这个loading是用多个div拼接而成的,而loading的颜色则是渲染div中的after伪元素的背景色:

/** 
 * .lds-spinner div:after {
        content: '';
        display: block;
        position: absolute;
        top: 3px;
        left: 29px;
        width: 5px;
        height: 14px;
        border-radius: 20%;
        background: #f3434a;
    }
*/
render() {
    return (
        <div className="i-loading">
            <div className="lds-spinner">
                {
                    Array(12).fill(0).map((item, index) => <div key={index}></div>)
                }
            </div>
        </div>
    );
}

这种方式如果要loading效果跟按钮字体保持一个颜色,则需要通过props传入一个色值,然后在Button组件中才能修改。实在是不太方便,我既要在CSS中设置按钮的颜色,还要通过props传给Button组件,当按钮的字体颜色需要更新时,则需要修改两个地方。再一个,loading图案的大小也要跟着按钮的大小进行变化,而div整体不太好调整,能想到的方法是使用 transform: scale 来对loading图案放大或者缩小。

这时,loading效果用SVG实现最好了,配上 currentColor ,能天然继承其父级元素的color值。再使用 em 单位适应按钮的大小:

render() {
    return (
        <div>
            <i className="i-icon-loading">
                <svg viewBox="0 0 1024 1024" className="i-icon-loading-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false">
                    <path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path>
                </svg>
            </i>
            <span>立即领取</span>
        </div>
    );
}

2. loading效果

按钮最少要接收2个参数,一个是表示是否正在loading中,再一个是接收click事件:

/**
.i-button {
    .i-icon-loading {
        margin-right: 8px;
    }
}

.i-button-loading {
    position: relative;

    &:before{
        content: '';
        position: absolute;
        background-color: #ffffff;
        opacity: 0.4;
        top: -1px;
        right: -1px;
        bottom: -1px;
        left: -1px;
        z-index: 1;
        transition: opacity .2s;
        border-radius: inherit;
    }
}
*/

interface ButtonProps {
    loading: boolean;
    onClick: React.MouseEventHandler;
}

export default class Button extends React.Component<ButtonProps> {
    handleClick = (e: any) => {
        const { loading, onClick } = this.props;
        if (!!loading) {
            // 正在loading中时,直接返回
            return;
        }
        if (onClick) {
            (onClick as React.MouseEventHandler)(e);
        }
    }

    render() {
        const { loading } = this.props;

        // 将loading效果提取出来
        const iconNode = loading ? <IconLoading /> : null;

        // loading时添加loading的class
        const loddingClass = loading ? ' i-button-loading' : '';

        return (
            <div onClick={this.handleClick} className={"i-button" + loddingClass }>
                {iconNode}
                <span>{this.props.children}</span>
            </div>
        );
    }
}

loading的icon组件如下:

/**
.i-icon-loading {
    font-size: 36px;
    transition: transform .3s ease-in-out;
    transition: transform .3s ease-in-out;
    will-change: transform;
    display: inline-block;
    color: inherit;
    font-style: normal;
    line-height: 0;
    text-align: center;
    text-transform: none;
    vertical-align: -0.125em;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;

    .i-icon-loading-spin {
        display: inline-block;
        animation: loadingCircle 1s infinite linear;
    }
    
    @-webkit-keyframes loadingCircle {
        100% {
            -webkit-transform: rotate(360deg);
            transform: rotate(360deg)
        }
    }
    
    @keyframes loadingCircle {
        100% {
            -webkit-transform: rotate(360deg);
            transform: rotate(360deg)
        }
    }
}
 * */
export default class IconLoading extends Component {
    render() {
        return (
            <i className="i-icon-loading">
                <svg viewBox="0 0 1024 1024" className="i-icon-loading-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false">
                    <path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path>
                </svg>
            </i>
        );
    }
}

3. 总结

第一次开始使用React写项目,之前都是用Vue在写项目。用Vue写了几个项目了,想着换一种技术栈来写项目,React跟Vue有一些相似之处,不过还是有些不同的,感觉React中很多地方需要自己完善,比如react的路由守卫beforeEach,CSS没有scoped属性,老担心不同的组件之前会产生样式冲突,虽然可以使用 *.module.css 来规避,不过这样在jsx中写className就不太方便了!另一方面来讲,写React特别能锻炼自己的js能力,比如高阶组件、ES6等新特性的使用,同时react和typescript结合的特别好。以后,随着对React更深入的了解,当然,这些也就不是问题了!

参考:

https://www.zhangxinxu.com/wordpress/2014/07/svg-sprites-fill-color-currentcolor/

@2019-05-10 21:16 评论(0)


以上所述就是小编给大家介绍的《React:实现一个带有loading效果的按钮组件》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning Vue.js 2

Learning Vue.js 2

Olga Filipova / Packt Publishing / 2017-1-5 / USD 41.99

About This Book Learn how to propagate DOM changes across the website without writing extensive jQuery callbacks code.Learn how to achieve reactivity and easily compose views with Vue.js and unders......一起来看看 《Learning Vue.js 2》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具