layer是前端开发中经常用到的组件,有不同的形态,最近在做新项目,目前github上合适的layer并不多,而且很多缺陷,只能自己搞了,原理很简单,不怕麻烦自己可以试试。
Reactjs主要是通过控制组件状态来改变UI,组件状态是通过action触发(纯函数),DLayer组件包含了5种:
1、alert 基础弹窗,只弹提示,只有确定按钮

2、confirm 确认弹窗,需要用户反馈,默认有确认和取消,文字可以自定义

3、tips 悬浮提示,2s后自动消失

4、loading 加载状态 模态窗

5、这个是项目定制的,悬浮窗带关闭按钮,针对文本比较多的情况
下面直接上代码:
index.jsx
import React, {PropTypes,Component} from 'react';
import AlertBox from 'components/common/layer/alertbox';
import NoticeBox from 'components/common/layer/noticebox';
import ConfirmBox from 'components/common/layer/confirmbox';
import TipsBox from 'components/common/layer/tipsbox';
import LoadingBox from 'components/common/layer/loadingbox';
const propTypes = {
width: PropTypes.number,
height: PropTypes.number,
onConfirm: PropTypes.func,
confirmTxtL: PropTypes.string,
confirmTxtR: PropTypes.string,
onCancel: PropTypes.func,
onClose: PropTypes.func,
visible: PropTypes.bool,
showMask: PropTypes.bool,
showClose: PropTypes.bool,
maskClick: PropTypes.bool,
animation: PropTypes.bool,
duration: PropTypes.number,
measure: PropTypes.string,
title: PropTypes.string,
message: PropTypes.string,
type: PropTypes.string
};
const defaultProps = {
width: 8,
height: 3,
measure: 'rem',
visible: false,
showMask: true,
maskClick: true, // 是否点击遮罩关闭模态窗口
animation: false,
duration: 300,
title: '',
message: '默认提示信息',
type: 'alert',
confirmTxtL: '确定',
confirmTxtR: '取消'
};
export default class DLayer extends Component {
constructor(props) {
super(props);
this.animationEnd = this.animationEnd.bind(this);
this.state = {
isShow: false,
animationType: 'leave'
}
}
hideByMask() {
if (this.props.maskClick) {
//this.props.onClose;
} else {
return false;
}
}
componentDidMount() {
if (this.props.visible) {
this.enter();
}
}
componentWillReceiveProps(nextProps) {
if (!this.props.visible && nextProps.visible) {
this.enter();
} else if (this.props.visible && !nextProps.visible) {
this.leave();
}
}
enter() {
this.setState({
isShow: true,
animationType: 'enter'
});
this.lockBody(true);
}
leave() {
this.setState({
animationType: 'leave'
});
this.lockBody(false);
}
animationEnd() {
if (this.state.animationType === 'leave') {
this.setState({
isShow: false
});
}
}
lockBody(state){
document.body.style.overflowY = state ? 'hidden' : 'auto';
document.body.style.position = state ? 'absolute' : 'static';
document.body.style.top = state ? document.body.scrollTop * -1 + 'px' : 'auto';
}
render() {
const {type,visible} = this.props;
let currentObj;
if (type == 'alert') {
currentObj = <AlertBox {...this.props}/>;
} else if (type == 'confirm') {
currentObj = <ConfirmBox {...this.props} />;
} else if (type == 'tips') {
currentObj = <TipsBox {...this.props} />;
} else if (type == 'loading') {
currentObj = <LoadingBox {...this.props} />;
} else if (type == 'notice') {
currentObj = <NoticeBox {...this.props} />;
}
const style = {
display: this.state.isShow ? 'block' : 'none',
WebkitAnimationDuration: this.props.duration + 'ms',
animationDuration: this.props.duration + 'ms'
};
const maskState = type == 'loading' ? false : true;
const layerState = visible ? 'enter' : 'leave';
return (
<div ref='layerBody' className={'layer-wrapper layer-fade-' + this.state.animationType} style={ style }
onAnimationEnd={ this.animationEnd }>
<div className='layer-mask' style={{ display : visible && this.props.showMask ? 'block' : 'none' }}
onClick={this.hideByMask.bind(this)}></div>
<div className={'layer-body ' + type + ' layer-zoom-' + this.state.animationType} style={ style }>
{currentObj}
</div>
</div>
)
}
}
DLayer.defaultProps = defaultProps;
DLayer.propTypes = propTypes;alertbox.jsx
import React, {PropTypes,Component} from 'react';
export default class AlertBox extends Component{
render(){
const title = this.props.title ? <h3 className='alert-title'>{this.props.title}</h3> : '';
return (
<div className='alert-block' type="layer">
<div className="icon-close" onClick={this.props.onClose} style={{display : this.props.showClose ? 'block' : 'none'}}></div>
{title}
<p className="alert-body">{this.props.message}</p>
<div className="alert-footer">
<ul className="averagebox">
<li className='confirm-btn' onClick={this.props.onConfirm}>确定</li>
</ul>
</div>
</div>
)
}
}confirmbox.jsx
import React, {PropTypes,Component} from 'react';
export default class ConfirmBox extends Component{
render(){
const title = this.props.title ? <h3 className='alert-title'>{this.props.title}</h3> : '';
return (
<div className={this.props.animation ? 'alert-block show-layer' : 'alert-block'} type="layer">
<div className="icon-close" onClick={this.props.onClose} style={{display : this.props.showClose ? 'block' : 'none'}}></div>
{title}
<div className="alert-body" dangerouslySetInnerHTML={{__html: this.props.message}}/>
<div className="alert-footer">
<ul className="averagebox">
<li className='confirm-btn' onClick={this.props.onConfirm}>{this.props.confirmTxtL}</li>
<li className='confirm-btn' onClick={this.props.onCancel}>{this.props.confirmTxtR}</li>
</ul>
</div>
</div>
)
}
}loadingbox.jsx
import React, {PropTypes,Component} from 'react';
export default class LoadingBox extends Component{
render(){
return (
<div className = "alert-loading" type="layer">
<div className = "loading-icon"></div>
<p>请等待...</p>
</div>
)
}
}tipsbox.jsx
import React, {PropTypes,Component} from 'react';
export default class TipsBox extends Component{
render(){
return (
<div className={this.props.animation ? 'alert-tip show-layer' : 'alert-tip'} type="layer">
<p>{this.props.message}</p>
</div>
)
}
}_dlayer.scss
$divide: 10;
$pswWidth: 375;
$ppr: 375/$divide/1;// 定义单位,以iphone6为标准
@mixin font-dpr($font-size){
font-size: #{$font-size / $ppr}rem;
}
@mixin toGapRem($property, $values...) {
$max: length($values);
$remValues: '';
@for $i from 1 through $max {
$newValue : nth($values, $i);
$value : '';
@if $newValue == auto {
$value : $newValue;
$remValues: #{$remValues + $value};
}@else{
$value: $newValue * $divide / $pswWidth;
$remValues: #{$remValues + $value}rem;
}
@if $i < $max {
$remValues: #{$remValues + " "};
}
}
#{$property}: $remValues;
}
/*遮罩*/
.layer-wrapper {
position: absolute;
z-index: 999999;
}
.layer-mask {
position: fixed;
z-index: 1000;
top: -20em;
left: 0;
bottom: -20em;
right: 0;
background: rgba(0, 0, 0, 0.5);
}
.layer-body {
position: fixed;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
text-align: center;
top: 40%;
z-index: 19870426;
}
.layer-body.confirm {
top: 35%;
}
.layer-body.loading {
@include toGapRem(width, 125);
}
.layer-body.alert, .layer-body.confirm {
@include toGapRem(width, 270);
}
.layer-body.tips {
@include toGapRem(width, 200);
}
.layer-body.notice {
@include toGapRem(width, 320);
top: 15%;
p {
@include font-dpr(12);
}
.icon-guanbi1 {
color: #FFFFFF;
@include font-dpr(36);
text-align: center;
@include toGapRem(margin-top, 10);
display: block;
}
}
.layer-body.notice .alert-block {
@include toGapRem(padding, 10, 0);
.alert-title {
color: #af292c;
}
.alert-body {
@include toGapRem(max-height, 375);
}
}
.alert-block {
width: 100%;
@include toGapRem(border-radius, 10);
background: #ffffff;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
@include toGapRem(padding, 10, 0, 0);
> .alert-body {
max-height: 8em;
background: #FFFFFF;
overflow-x: hidden;
@include toGapRem(padding, 20);
@include font-dpr(15);
text-align: left;
color: #111111;
@include toGapRem(line-height, 17);
margin:0;
h1 {
@include font-dpr(14);
color: #111111;
@include toGapRem(margin, 20, 0, 15, 0);
}
p {
@include font-dpr(12);
color: #333333;
@include toGapRem(line-height, 17);
max-height: 9999px;
}
}
> .alert-title {
@include font-dpr(15);
text-align: center;
@include toGapRem(padding, 12, 0, 10, 0);
font-weight: bold;
margin: 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
color: #111111;
}
> .alert-footer {
position: relative;
text-align: center;
border-top: 1px solid #CCCCCC;
background: #FFFFFF;
@include font-dpr(15);
@include toGapRem(border-bottom-left-radius, 10);
@include toGapRem(border-bottom-right-radius, 10);
ul {
margin: 0;
padding: 0;
li {
position: relative;
text-align: center;
cursor: pointer;
@include toGapRem(height, 45);
@include toGapRem(line-height, 45);
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-box-flex: 1;
list-style-type: none;
color: #0066cc;
}
}
}
> .alert-footer li:nth-child(2):before {
content: '\20';
position: absolute;
width: 1px;
height: 100%;
left: 0;
top: 0;
background-color: #aaaaaa;
}
> .icon-close {
font-size: .4rem;
color: #999;
position: absolute;
right: .8em;
top: .3rem;
}
}
.averagebox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-flex-flow: wrap;
justify-content: space-around;
}
.alert-tip {
background: #000000;
opacity: 0.7;
width: 100%;
@include toGapRem(border-radius, 5);
> p {
@include toGapRem(padding, 9, 25);
text-align: center;
@include font-dpr(14);
color: #FFFFFF;
}
}
.alert-loading {
@include toGapRem(min-height, 90);
width: 100%;
position: fixed;
background: rgba(0, 0, 0, .5);
z-index: 19870428;
text-align: center;
@include toGapRem(border-radius, 8);
@include toGapRem(padding, 5);
> p {
color: #FFFFFF;
@include toGapRem(margin, 10, 0, 0);
@include font-dpr(14);
}
> .loading-icon {
width: 3em;
height: 3em;
margin: 0 auto;
background: url(images/ajaxLoading.gif);
background-size: 3em 3em;
@include toGapRem(margin-top, 10);
}
}
//定义动画
/* -- fade -- */
@-webkit-keyframes layer-fade-enter {
from {
opacity: 0;
}
}
@keyframes layer-fade-enter {
from {
opacity: 0;
}
}
.layer-fade-enter {
-webkit-animation: layer-fade-enter both ease-in;
animation: layer-fade-enter both ease-in;
}
@-webkit-keyframes layer-fade-leave {
to {
opacity: 0
}
}
@keyframes layer-fade-leave {
to {
opacity: 0
}
}
.layer-fade-leave {
-webkit-animation: layer-fade-leave both ease-out;
animation: layer-fade-leave both ease-out;
}
@-webkit-keyframes layer-zoom-enter {
from {
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
}
@keyframes layer-zoom-enter {
from {
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
}
.layer-zoom-enter {
-webkit-animation: layer-zoom-enter both cubic-bezier(0.4, 0, 0, 1.5);
animation: layer-zoom-enter both cubic-bezier(0.4, 0, 0, 1.5);
}
@-webkit-keyframes layer-zoom-leave {
to {
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
}
@keyframes layer-zoom-leave {
to {
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
}
.layer-zoom-leave {
-webkit-animation: layer-zoom-leave both;
animation: layer-zoom-leave both;
}调用方式:
1、页面引入
import DLayer from 'components/common/layer';//index.jsx
let {layerTitle,layerType,layerShow,showMask,layerMsg,animation,confirmTxtL,confirmTxtR,confirm,cancel} = this.props.layerInfo;
<DLayer
title={layerTitle}
type={layerType}
visible={layerShow}
message={layerMsg}
animation={animation}
showMask={showMask}
confirmTxtL={confirmTxtL}
confirmTxtR={confirmTxtR}
onConfirm={confirm}
onCancel={cancel}/>注意: layerInfo 状态通过redux维护
2、调用:
alert:
pageConfig.alert(this, 'hello Word');
confirm:
pageConfig.confirm(this, {msg: 'This is msg', title: '提示', confirmTxtL: 'Yes', confirmTxtR: 'No'}, ()=> {
}, ()=> {
native.callPhone();
});tips
pageConfig.tips(this, 'hello Word');
loading
pageConfig.alert(this, true);
pageConfig.js
alert(_this, msg, confirm = ()=> {
}){
if(Object.keys(_this).length === 0) return false;
let action = _this.props.actions.queryLayerInfo;
action({
title:'',
layerShow: true, layerType: 'alert', showMask: true,
layerMsg: msg, confirm: ()=> {
action({layerShow: false});
confirm();
}
});
},
confirm(_this, params, confirm = ()=> {
}, cancel = ()=> {
}){
let action = _this.props.actions.queryLayerInfo;
action({
layerShow: true,
layerType: 'confirm',
layerMsg: params.msg,
layerTitle: params.title,
showMask: true,
confirmTxtL: params.confirmTxtL,
confirmTxtR: params.confirmTxtR,
confirm: ()=> {
action({layerShow: false});
confirm();
},
cancel: ()=> {
action({layerShow: false});
cancel();
}
});
},
tips(_this, msg, callback = ()=> {
}){
let action = _this.props.actions.queryLayerInfo;
action({layerShow: true, layerType: 'tips', showMask: false, layerMsg: msg});
setTimeout(()=> {
action({layerShow: false});
callback()
}, 2000);
},
loading(_this, state){
let action = _this.props.actions.queryLayerInfo;
action({layerShow: state, layerType: 'loading', showMask: false})
}