React:实现一个带有loading效果的按钮组件
侧边栏壁纸
  • 累计撰写 1,061 篇文章
  • 累计收到 0 条评论

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

加速器之家
2024-10-20 / 0 评论 / 4 阅读 / 正在检测是否收录...

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

我们实现带有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/

0

评论

博主关闭了当前页面的评论