基于React的模态窗实现—DLayer
layer是前端开发中经常用到的组件,有不同的形态,最近在做新项目,目前github上合适的layer并不多,而且很多缺陷,只能自己搞了,原理很简单,不怕麻烦自己可以试试
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}) }
很赞哦! ( 1
)