在业务经常需要点击按钮去请求接口,在接口还未返回结果前,需要给用户一个等待的感觉,同时锁住按钮,防止产生二次点击!
我们实现带有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/