基于React.js网页版弹窗|react pc端自定义对话框组件RLayer
基于React.js实现PC桌面端自定义弹窗组件RLayer。
前几天有分享一个Vue网页版弹框组件,今天分享一个最新开发的React PC桌面端自定义对话框组件。
RLayer 一款基于react.js开发的PC端自定义Layer弹出框组件。支持超过30+参数自由配置,通过轻巧的布局设计、极简的调用方式来解决复杂的弹出层功能,为您呈上不一样的弹窗效果。

RLayer在设计开发之初有参考之前的VLayer组件,尽量保持功能效果的一致性。

如上图:展示一些常用的基础普通型弹窗功能。
- 极简调用方式 rlayer({....})
- 12+弹框类型(toast、footer、actionsheet|actionsheetPicker、android|ios、contextmenu、drawer、iframe、message|notify|popover)
- 7+动画效果(scaleIn | fadeIn | footer | fadeInUp | fadeInDown | fadeInLeft | fadeInRight)
◆ 快速引入
在需要使用弹窗功能页面引入rlayer组件。
// 引入组件RLayer
import rlayer from './components/rlayer'
rlayer目前只支持函数式调用方式。 rlayer({...})
const showConfirm = () => {
let $el = rlayer({
title: '标题信息',
content: "<div style='color:#0070f3;padding:30px;'>这里是确认框提示信息!</div>",
shadeClose: false,
zIndex: 1001,
lockScroll: false,
resize: true,
dragOut: true,
btns: [
{
text: '取消',
click: () => {
$el.close()
}
},
{
text: '确定',
style: {color: '#61dafb'},
click: () => {
// ...
}
}
]
})
}
注意:当弹窗类型为 message | notify | popover 需要通过如下方式调用。
rlayer.message({...}) rlayer.notify({...}) rlayer.popover({...})
◆ 一睹芳容
















◆ 编码实现
rlayer支持如下参数随意搭配使用。
/**
* 弹出框默认配置
*/
static defaultProps = {
// 参数
id: '', // {string} 控制弹层唯一标识,相同id共享一个实例
title: '', // {string} 标题
content: '', // {string|element} 内容(支持字符串或组件)
type: '', // {string} 弹框类型(toast|footer|actionsheet|actionsheetPicker|android|ios|contextmenu|drawer|iframe)
layerStyle: '', // {object} 自定义弹框样式
icon: '', // {string} Toast图标(loading|success|fail)
shade: true, // {bool} 是否显示遮罩层
shadeClose: true, // {bool} 是否点击遮罩层关闭弹框
lockScroll: true, // {bool} 是否弹框显示时将body滚动锁定
opacity: '', // {number|string} 遮罩层透明度
xclose: true, // {bool} 是否显示关闭图标
xposition: 'right', // {string} 关闭图标位置(top|right|bottom|left)
xcolor: '#333', // {string} 关闭图标颜色
anim: 'scaleIn', // {string} 弹框动画(scaleIn|fadeIn|footer|fadeInUp|fadeInDown|fadeInLeft|fadeInRight)
position: 'auto', // {string|array} 弹框位置(auto|['150px','100px']|t|r|b|l|lt|rt|lb|rb)
drawer: '', // {string} 抽屉弹框(top|right|bottom|left)
follow: null, // {string|array} 跟随定位弹框(支持.xxx #xxx 或 [e.clientX,e.clientY])
time: 0, // {number} 弹框自动关闭秒数(1|2|3...)
zIndex: 8090, // {number} 弹框层叠
topmost: false, // {bool} 是否置顶当前弹框
area: 'auto', // {string|array} 弹框宽高(auto|'250px'|['','200px']|['650px','300px'])
maxWidth: 375, // {number} 弹框最大宽度(只有当area:'auto'时设定才有效)
maximize: false, // {bool} 是否显示最大化按钮
fullscreen: false, // {bool} 是否全屏弹框
fixed: true, // {bool} 是否固定弹框
drag: '.rlayer__wrap-tit', // {string|bool} 拖拽元素(可自定义拖动元素drag:'#xxx' 禁止拖拽drag:false)
dragOut: false, // {bool} 是否允许拖拽到浏览器外
lockAxis: null, // {string} 限制拖拽方向可选: v 垂直、h 水平,默认不限制
resize: false, // {bool} 是否允许拉伸弹框
btns: null, // {array} 弹框按钮(参数:text|style|disabled|click) // 事件
success: null, // {func} 层弹出后回调
end: null, // {func} 层销毁后回调
}
rlayer组件模板
render() {
let opt = this.state
return (
<>
<div className={domUtils.classNames('rui__layer', {'rui__layer-closed': opt.closeCls})} id={opt.id} style={{display: opt.opened?'block':'none'}}>
{/* 遮罩 */}
{ opt.shade && <div className="rlayer__overlay" onClick={this.shadeClicked} style={{opacity: opt.opacity}}></div> }
<div className={domUtils.classNames('rlayer__wrap', opt.anim&&'anim-'+opt.anim, opt.type&&'popui__'+opt.type)} style={{...opt.layerStyle}}>
{ opt.title && <div className='rlayer__wrap-tit' dangerouslySetInnerHTML={{__html: opt.title}}></div> }
<div className='rlayer__wrap-cntbox'>
{ opt.content ?
<>
{
opt.type == 'iframe' ?
(
<iframe scrolling='auto' allowtransparency='true' frameBorder='0' src={opt.content}></iframe>
)
:
(opt.type == 'message' || opt.type == 'notify' || opt.type == 'popover') ?
(
<div className='rlayer__wrap-cnt'>
{ opt.icon && <i className={domUtils.classNames('rlayer-msg__icon', opt.icon)} dangerouslySetInnerHTML={{__html: opt.messageIcon[opt.icon]}}></i> }
<div className='rlayer-msg__group'>
{ opt.title && <div className='rlayer-msg__title' dangerouslySetInnerHTML={{__html: opt.title}}></div> }
{ typeof opt.content == 'string' ?
<div className='rlayer-msg__content' dangerouslySetInnerHTML={{__html: opt.content}}></div>
:
<div className='rlayer-msg__content'>{opt.content}</div>
}
</div>
</div>
)
:
(
typeof opt.content == 'string' ?
(<div className='rlayer__wrap-cnt' dangerouslySetInnerHTML={{__html: opt.content}}></div>)
:
opt.content
)
}
</>
:
null
}
</div>
{ opt.btns && <div className='rlayer__wrap-btns'>
{
opt.btns.map((btn, index) => {
return <span className={domUtils.classNames('btn')} key={index} style={{...btn.style}} dangerouslySetInnerHTML={{__html: btn.text}}></span>
})
}
</div>
}
{ opt.xclose && <span className={domUtils.classNames('rlayer__xclose')}></span> }
{ opt.maximize && <span className='rlayer__maximize'></span> }
{ opt.resize && <span className='rlayer__resize'></span> }
</div>
<div className='rlayer__dragfix'></div>
</div>
</>
)
}
/**
* @Desc ReactJs|Next.js自定义弹窗组件RLayer
* @Time andy by 2020-12-04
* @About Q:282310962 wx:xy190310
*/
import React from 'react'
import ReactDOM from 'react-dom' // 引入操作类
import domUtils from './utils/dom' let $index = 0, $lockCount = 0, $timer = {} class RLayerComponent extends React.Component {
static defaultProps = {
// ...
} constructor(props) {
super(props)
this.state = {
opened: false,
closeCls: '',
toastIcon: {
// ...
},
messageIcon: {
// ...
},
rlayerOpts: {},
tipArrow: null,
} this.closeTimer = null
} componentDidMount() {
window.addEventListener('resize', this.autopos, false)
}
componentWillUnmount() {
window.removeEventListener('resize', this.autopos, false)
clearTimeout(this.closeTimer)
} /**
* 打开弹框
*/
open = (options) => {
options.id = options.id || `rlayer-${domUtils.generateId()}` this.setState({
...this.props, ...options, opened: true,
}, () => {
const { success } = this.state
typeof success === 'function' && success.call(this) this.auto()
this.callback()
})
} /**
* 关闭弹框
*/
close = () => {
const { opened, time, end, remove, rlayerOpts, action } = this.state
if(!opened) return this.setState({ closeCls: true })
clearTimeout(this.closeTimer)
this.closeTimer = setTimeout(() => {
this.setState({
closeCls: false,
opened: false,
})
if(rlayerOpts.lockScroll) {
$lockCount--
if(!$lockCount) {
document.body.style.paddingRight = ''
document.body.classList.remove('rc-overflow-hidden')
}
}
if(time) {
$index--
}
if(action == 'update') {
document.body.style.paddingRight = ''
document.body.classList.remove('rc-overflow-hidden')
}
rlayerOpts.isBodyOverflow && (document.body.style.overflow = '')
remove()
typeof end === 'function' && end.call(this)
}, 200);
} // 弹框位置
auto = () => {
// ... this.autopos() // 全屏弹框
if(fullscreen) {
this.full()
} // 弹框拖拽|缩放
this.move()
} autopos = () => {
const { opened, id, fixed, follow, position } = this.state
if(!opened) return
let oL, oT
let dom = document.querySelector('#' + id)
let rlayero = dom.querySelector('.rlayer__wrap') if(!fixed || follow) {
rlayero.style.position = 'absolute'
} let area = [domUtils.client('width'), domUtils.client('height'), rlayero.offsetWidth, rlayero.offsetHeight] oL = (area[0] - area[2]) / 2
oT = (area[1] - area[3]) / 2 if(follow) {
this.offset()
} else {
typeof position === 'object' ? (
oL = parseFloat(position[0]) || 0, oT = parseFloat(position[1]) || 0
) : (
position == 't' ? oT = 0 :
position == 'r' ? oL = area[0] - area[2] :
position == 'b' ? oT = area[1] - area[3] :
position == 'l' ? oL = 0 :
position == 'lt' ? (oL = 0, oT = 0) :
position == 'rt' ? (oL = area[0] - area[2], oT = 0) :
position == 'lb' ? (oL = 0, oT = area[1] - area[3]) :
position == 'rb' ? (oL = area[0] - area[2], oT = area[1] - area[3]) :
null
) rlayero.style.left = parseFloat(fixed ? oL : domUtils.scroll('left') + oL) + 'px'
rlayero.style.top = parseFloat(fixed ? oT : domUtils.scroll('top') + oT) + 'px'
}
} // 跟随元素定位
offset = () => {
const { id, follow } = this.state
let oW, oH, pS
let dom = document.querySelector('#' + id)
let rlayero = dom.querySelector('.rlayer__wrap') oW = rlayero.offsetWidth
oH = rlayero.offsetHeight
pS = domUtils.getFollowRect(follow, oW, oH) rlayero.style.left = pS[0] + 'px'
rlayero.style.top = pS[1] + 'px'
} // 最大化弹框
full = () => {
// ...
} // 恢复弹框
restore = () => {
// ...
} // 拖拽|缩放弹框
move = () => {
// ...
} // 事件处理
callback = () => {
const { time } = this.state
// 倒计时关闭弹框
if(time) {
$index++
// 防止重复计数
if($timer[$index] != null) clearTimeout($timer[$index])
$timer[$index] = setTimeout(() => {
this.close()
}, parseInt(time) * 1000);
}
} // 点击最大化按钮
maximizeClicked = (e) => {
let o = e.target
if(o.classList.contains('maximized')) {
// 恢复
this.restore()
} else {
// 最大化
this.full()
}
} // 点击遮罩层
shadeClicked = () => {
if(this.state.shadeClose) {
this.close()
}
} // 按钮事件
btnClicked = (index, e) => {
let btn = this.state.btns[index]
if(!btn.disabled) {
typeof btn.click === 'function' && btn.click(e)
}
} render() {
let opt = this.state
return (
<>
<div className={domUtils.classNames('rui__layer', {'rui__layer-closed': opt.closeCls})} id={opt.id} style={{display: opt.opened?'block':'none'}}>
{ opt.shade && <div className="rlayer__overlay" onClick={this.shadeClicked} style={{opacity: opt.opacity}}></div> }
<div className={domUtils.classNames('rlayer__wrap', opt.anim&&'anim-'+opt.anim, opt.type&&'popui__'+opt.type, opt.drawer&&'popui__drawer-'+opt.drawer, opt.xclose&&'rlayer-closable', opt.tipArrow)} style={{...opt.layerStyle}}>
{ opt.title && <div className='rlayer__wrap-tit' dangerouslySetInnerHTML={{__html: opt.title}}></div> }
{ opt.type == 'toast' && opt.icon ? <div className={domUtils.classNames('rlayer__toast-icon', 'rlayer__toast-'+opt.icon)} dangerouslySetInnerHTML={{__html: opt.toastIcon[opt.icon]}}></div> : null }
<div className='rlayer__wrap-cntbox'>
{ opt.content ?
<>
{
opt.type == 'iframe' ?
(
<iframe scrolling='auto' allowtransparency='true' frameBorder='0' src={opt.content}></iframe>
)
:
(opt.type == 'message' || opt.type == 'notify' || opt.type == 'popover') ?
(
<div className='rlayer__wrap-cnt'>
{ opt.icon && <i className={domUtils.classNames('rlayer-msg__icon', opt.icon)} dangerouslySetInnerHTML={{__html: opt.messageIcon[opt.icon]}}></i> }
<div className='rlayer-msg__group'>
{ opt.title && <div className='rlayer-msg__title' dangerouslySetInnerHTML={{__html: opt.title}}></div> }
{ typeof opt.content == 'string' ?
<div className='rlayer-msg__content' dangerouslySetInnerHTML={{__html: opt.content}}></div>
:
<div className='rlayer-msg__content'>{opt.content}</div>
}
</div>
</div>
)
:
(
typeof opt.content == 'string' ?
(<div className='rlayer__wrap-cnt' dangerouslySetInnerHTML={{__html: opt.content}}></div>)
:
opt.content
)
}
</>
:
null
}
</div>
{/* btns */}
{ opt.btns && <div className='rlayer__wrap-btns'>
{
opt.btns.map((btn, index) => {
return <span className={domUtils.classNames('btn')} key={index} style={{...btn.style}} dangerouslySetInnerHTML={{__html: btn.text}}></span>
})
}
</div>
}
{ opt.xclose && <span className={domUtils.classNames('rlayer__xclose')} style={{color: opt.xcolor}}></span> }
{ opt.maximize && <span className='rlayer__maximize'></span> }
{ opt.resize && <span className='rlayer__resize'></span> }
</div>
<div className='rlayer__dragfix'></div>
</div>
</>
)
}
}
其中utils/dom.js中是一些常用操作函数。
为了方便在react.js中动态操作className,于是抽离了classnames函数。
classNames: function() {
var hasOwn = {}.hasOwnProperty;
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
非常轻松方便的在react中实现各种动态操作className。
<div className="rlayer"></div>
<div className={domUtils.classNames('rlayer', {'rlayer__closed': opt.close})}></div>
<div className={domUtils.classNames('rlayer', opt.anim&&'anim-'+opt.anim)}></div>
<div className={domUtils.classNames('rlayer', opt.icon)}></div>
...
react.js中通过ReactDOM.render方法将弹窗组件挂载到body上。
function RLayer(options = {}) {
let $id = options.id
let $dom = document.querySelector('#' + $id)
if($id && $dom) return
const div = document.createElement('div')
const ref = React.createRef()
document.body.appendChild(div)
/*
ReactDOM.render(
<RLayerComponent {...options} remove={()=>{
ReactDOM.unmountComponentAtNode(div)
document.body.removeChild(div)
}} />,
div
)
*/
ReactDOM.render(<RLayerComponent ref={ref} />, div)
ref.current.open({ ...options, remove() {
if(!ref.current) return
ReactDOM.unmountComponentAtNode(div)
document.body.removeChild(div)
}})
// 返回弹框实例
return ref.current
}
rlayer.js组件支持自定义拖拽区域 (drag:'#xxx'),是否拖动到窗口外 (dragOut:true)。还支持iframe弹窗类型 (type:'iframe')。

另外rlayer.js还支持弹窗置顶 (topmost:true),永远保持当前窗口在最前。

好了,以上就是基于react.js开发PC端弹窗的相关介绍。希望大家能喜欢哈~~ ✍✍

最后分享两个vue自定义组件
vue自定义对话框组件:https://www.cnblogs.com/xiaoyan2017/p/13913860.html
vue自定义滚动条组件:https://www.cnblogs.com/xiaoyan2017/p/14062703.html
基于React.js网页版弹窗|react pc端自定义对话框组件RLayer的更多相关文章
- Vue.js 桌面端自定义滚动条组件|vue美化滚动条VScroll
基于vue.js开发的小巧PC端自定义滚动条组件VScroll. 前段时间有给大家分享一个vue桌面端弹框组件,今天再分享最近开发的一个vue pc端自定义滚动条组件. vscroll 一款基于vue ...
- react.js插件开发,x-dailog弹窗浮层组件
react.js插件开发,x-dailog弹窗浮层组件 我认为,每一个组件都应该有他自带的样式和属性事件回调配置.所以我会给x-dialog默认一套简单的样式,和各种默认的配置项.所有react插件示 ...
- React.js 小书 Lesson5 - React.js 基本环境安装
作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson5 转载请注明出处,保留原文链接和作者信息. 安装 React.js React.js 单独使 ...
- 基于WebSocket实现网页版聊天室
WebSocket ,HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,其使用简单,应用场景也广泛,不同开发语言都用种类繁多的实现,仅Java体系中,Tomcat,Jetty,Sp ...
- [应用][js+css3]3D盒子导航[PC端]
CSS3构建的3D盒子之导航应用 1.在用css3构建的盒子表面,放上iframe,来加载导航页面. 2.鼠标左键按下移动可旋转盒子,寻找想要的网址. 3.左键单机盒子表面,将全屏现实所点盒子表面的网 ...
- js判断用户是在PC端或移动端访问
js如何判断用户是在PC端和还是移动端访问. 最近一直在忙我们团队的项目“咖啡之翼”,在这个项目中,我们为移动平台提供了一个优秀的体验.伴随Android平台的红火发展.不仅带动国内智能手机行业,而 ...
- js实现一个可以兼容PC端和移动端的div拖动效果
前段时间写了一个简单的div拖动效果,不料昨天项目上正好需要一个相差不多的需求,就正好用上了,但是在移动端的时候却碰到了问题,拖动时候用到的三个事件:mousedown.mousemove.mouse ...
- JS判断android ios系统 PC端和移动端
最近公司上线移动端,需要根据不同的系统跳转到不同的产品页面,百度后发现这一段代码很好用,不但可以判断当前是什么系统,还能知道当前浏览器是什么内核,移动端PC端都已测试无问题! var browser ...
- Js 网页版扫雷游戏代码实现
这个游戏是自己在大约一年前联系js熟练度时做的,用的都是基础的东西,最近比较忙没时间整理.直接发给大家,有兴趣的可以看一下.欢迎大家提出建议.如果你有什么新的想法也可以提出来,或者你并不擅长编程.你想 ...
随机推荐
- Es6-Promise初识
Promise 含义: Promise 是异步编程的一种解决方案,比传统的解决方案--回调函数和事件--更合理和更强大.它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Pro ...
- ubuntu 17.10 安装QQ
折腾一大堆 看报错信息 正在选中未选择的软件包 wine-qqintl:i386.(正在读取数据库 ... 系统当前共安装有 185429 个文件和目录.)正准备解包 wine-qqintl_0.1. ...
- Inception系列之Inception_v1
目前,神经网络模型为了得到更好的效果,越来越深和越来越宽的模型被提出.然而这样会带来以下几个问题: 1)参数量,计算量越来越大,在有限内存和算力的设备上,其应用也就越难以落地. 2)对于一些数据集较少 ...
- 2020年的UWP(3)——UWP和desktop extension的简单交互
上一篇<2020年的UWP(2)--In Process App Service>中我们了解了UWP和Desktop Extension可以通过AppService进行数据交互.本篇我们就 ...
- Dreamweaver是怎么把图片转换成代码 简单五步骤即可解决
Dreamweaver图片转换代码图文介绍 1.打开需要转换的Photoshop作品: 2.保存为web格式,得到一个文件夹和一个html格式文件: 3.在html格式文件上单击右键,选择打开方式为D ...
- 什么是LRU缓存淘汰机制
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰.该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历 ...
- itextpdf freemarker渲染
现有需求涉及到打印pdf操作,简单找了俩种方式 在现有的模板上进行编辑,操作难度比较大 通过freemarker生成静态页面,在进行转换html,完美. 关于动态生成pdf,网上参考的挺多的,看来看去 ...
- php xml转数组
<?php libxml_disable_entity_loader(true); $notify_values = json_decode(json_encode(simplexml_load ...
- java中保留两位小数的方法
1.BigDecimal的setScale // RoundingMode 舍入模式: // 1.UP/DOWN ->基准为数字0: // 2.CEILING/FLOOR ->基准为正负无 ...
- new与malloc的10点区别(转)
1. 申请的内存所在位置 new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存.自由存储区是C++基于new操作符的一个抽象概念,凡是通过ne ...