在平常的需求开发中咱们常常须要用到弹窗,那么在咱们构建弹窗时,在引用组件是都须要引入组件DOM,而后经过事件来控制组件的影藏显示,调用也不是很方便;css
本组件使用事前注册DOM的方式,业务方在使用时刻直接调用暴露的方法便可,无需再引用DOM。react
import newDialog from "*./components/newDialog"
newDialog.open({ title: "退出发布", content: '是否须要保存草稿箱?', btn: [{ text: "不保存" },{ text: "保存" }], callback: (event)=>{ if(event.result == 2){ //TODO }else { //TODO } } });
参数 | 说明 | 类型 | 是否必填 | |
---|---|---|---|---|
title | 弹窗标题 | String | √ | |
content | 弹窗展现内容 | String\ | ReactDom | √ |
btn | 弹窗按钮可配置一个或者两个 | Array | √ | |
callback | 弹窗响应回调方法 | Function | √ |
弹窗按钮说明json
//弹窗按钮是json数组配置的 最多配置两个 btn: [{ text: "不保存" },{ text: "保存" }], //每一个按钮只须要配置text按钮文案便可 样式和颜色都集成到css //若是配置一个按钮就是底部长按钮 配置两个根据配置的数组顺序从左向右展现
回调参数数组
//点击按钮会返回回调数据并关闭弹出 { result: 1/2 //返回按钮的索引值 }
js代码newDialog.tsxapp
'use strict'; import './newDialog.scss'; import * as React from 'react'; import cs from "classnames"; import * as ReactDOM from 'react-dom'; import { isIphoneX } from './../../util/util'; /** * @description 解决移动端滚动穿透事件 **/ const ModalHelper = (function (bodyCls) { var scrollTop; return { afterOpen: function () { scrollTop = document.scrollingElement.scrollTop; document.body.classList.add(bodyCls); document.body.style.top = -scrollTop + 'px'; }, beforeClose: function () { document.body.classList.remove(bodyCls); // scrollTop lost after set position:fixed, restore it back. document.scrollingElement.scrollTop = scrollTop; } }; })('modal-open'); interface IAppProps { title?: string; content?: any; btn?: any; callback?: Function; } interface IAppState { showToast?: Boolean; title?: string; content?: any; btn?: any; callback?: Function; isIPhoneX?: Boolean; } let DialogDiv; class Dialog extends React.Component<IAppProps, IAppState> { constructor(props: IAppProps) { super(props); this.state = { showToast: true, isIPhoneX: false }; } componentWillMount() { let isIPhoneX = isIphoneX(); this.setState({ isIPhoneX }); } static open = (options) => { const { ...props } = options || {}; DialogDiv = document.createElement('div'); setTimeout(()=>{ document.body.appendChild(DialogDiv); ReactDOM.render(<Dialog {...props}/> ,DialogDiv); },100); ModalHelper.afterOpen(); } close = (param) => { let result = param +1; this.props.callback({ result: result }); this.setState({ showToast: false }); document.body.removeChild(DialogDiv); ModalHelper.beforeClose(); } render() { const { showToast, isIPhoneX } = this.state; const { title, content, btn } = this.props; let _twoBtn = btn.length > 1 ? true : false; return ( <div className={cs('dialog-container', { show: showToast })}> <div className="pay-box"> <div className="new-dialog-box"> <div className="dialog-title"> {title} </div> <div className="dialog-content"> {content} </div> <div className={cs('dialog-bottom flex', { isIPhoneX: isIPhoneX })}> {btn.map((item, idx) => { return ( <div onClick={() => { this.close(idx); // item.callback(); }} className={cs('dialog-btn', { margin_r: _twoBtn && idx == 0, default: _twoBtn && idx == 0 })}> {item.text} </div> ); })} </div> </div> </div> </div> ) } } export default Dialog;
css代码newDialog.scssdom
$baseFontSize: 16px !default; // pixels to rems @function pxToRem($px) { @return $px / $baseFontSize * 1rem; } .dialog-container { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, .7); display: none; z-index: 9999; &.show { display: block; } } .new-dialog-box { width: 100%; min-height: pxToRem(195px); background: rgba(255, 255, 255, 1); position: absolute; box-sizing: border-box; margin: 0; bottom: 0; z-index: 9999; transition: all 0.3s; border-radius: pxToRem(8px) pxToRem(8px) 0 0; // display: flex; .dialog-title { font-size: pxToRem(19px); margin: pxToRem(25px) auto pxToRem(25px); text-align: center; font-family: PingFangSC-Medium, PingFang SC; font-weight: 500; color: rgba(51, 51, 51, 1); line-height: pxToRem(19px); } .dialog-content { padding: 0 pxToRem(25px); font-size: pxToRem(14px); font-family: PingFangSC-Regular, PingFang SC; color: rgba(51, 51, 51, 1); margin: 0 auto pxToRem(15px); text-align: center; color: rgba(0, 0, 0, 1); line-height: pxToRem(27px); min-height: pxToRem(50px); } .dialog-bottom { height: pxToRem(45px); padding: 0 pxToRem(20px); margin-bottom: pxToRem(15px); display: flex; &.isIPhoneX { margin-bottom: pxToRem(49px); } } .dialog-btn { // margin: 0 auto pxToRem(30px); // width: pxToRem(300px); flex: 1; height: pxToRem(45px); background-image: linear-gradient(-44deg, #FCAF3C 0%, #F25228 98%); box-shadow: 0 8px 14px 0 rgba(190,118,22,0.20); border-radius: pxToRem(45px); text-align: center; line-height: pxToRem(45px); font-size: pxToRem(15px); font-family: PingFangSC-Medium, PingFang SC; color: rgba(255, 255, 255, 1); &.margin_r { margin-right: pxToRem(24px); } &.default { background: #FFFFFF; border: 1px solid #E6E6E6; box-shadow: 0 10px 20px -8px rgba(173,173,173,0.30); border-radius: pxToRem(45px); font-size: pxToRem(15px); font-family: PingFang-SC-Medium, PingFang-SC; color: rgba(85, 85, 85, 1); } } }
另外附上代码中iPhone X系列机型判断方法iphone
export const isIphoneX = function() { // iPhone X、iPhone XS var isIPhoneX = /iphone/gi.test(window.navigator.userAgent) && window.devicePixelRatio && window.devicePixelRatio === 3 && window.screen.width === 375 && window.screen.height === 812; // iPhone XS Max var isIPhoneXSMax = /iphone/gi.test(window.navigator.userAgent) && window.devicePixelRatio && window.devicePixelRatio === 3 && window.screen.width === 414 && window.screen.height === 896; // iPhone XR var isIPhoneXR = /iphone/gi.test(window.navigator.userAgent) && window.devicePixelRatio && window.devicePixelRatio === 2 && window.screen.width === 414 && window.screen.height === 896; if (isIPhoneX || isIPhoneXSMax || isIPhoneXR) { return true; } return false; }