layer是前端开发中经常用到的组件,有不同的形态,最近在做新项目,目前github上合适的layer并不多,而且很多缺陷,只能自己搞了,原理很简单,不怕麻烦自己可以试试。

Reactjs主要是通过控制组件状态来改变UI,组件状态是通过action触发(纯函数),DLayer组件包含了5种:

1、alert  基础弹窗,只弹提示,只有确定按钮

5E8F04FF-1D85-4358-9B8D-F2E6130BCE91.png

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

E1E141AD-30A3-4B4D-B482-440F8B4E65FC.png

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

D310C7C8-3AED-401E-AF65-08D8C5423A47.png

4、loading  加载状态 模态窗

5E760234-538F-44EA-8141-F418F656C4D0.png

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})
}