平时项目使用弹窗,用 npm 仓库的就可以。
或者antd design 的也不错。
我尝试用react hook 实现一个弹窗。参数配置参考蚂蚁文档的部分。
支持: <Modal {...props}/>
方式调用
支持: Modal.sucess('...');
方式调用
支持:Modal.success({...porps});
方式调用
代码示例
export default function Main() {
const [isVisible, setIsVisible] = useState(false);
const showModal = () => {
setIsVisible(true);
};
const handleOk = () => {
setIsVisible(false);
};
const handleCancel = () => {
setIsVisible(false);
};
//函数式引用
const showInstance = () => {
Modal.success("ok");
};
return (
<div>
<button className={styles.btn} onClick={showModal}>
open Modal
</button>
<button className={styles.btn} onClick={showInstance}>
实例弹窗
</button>
<Modal
title="标题"
visible={isVisible}
onOk={handleOk}
onCancel={handleCancel}
style={{ borderRadius: "4px", background: "#202229" }} // 设置浮层的样式
width={520} //设置浮层的宽度
centered={true} //垂直居中显示
closable={true} //是否显示右上角关闭按钮 默认显示true
closeIcon = {} //自定义关闭按钮
mask={true} //是否显示遮罩 默认显示true
maskCloseable={false} //点击遮罩是否关闭 默认关闭true
maskStyle={{ background: "rgba(0, 0, 0, 0.85)" }} //遮罩样式
okText="确认"
cancelText="取消"
footer={
<div>
<button onClick={handleOk}>测试取消</button>
</div>
}
>
<div style={{ color: "#fff" }}>这里是自定义的内容</div>
</Modal>
</div>
);
}
复制代码
代码结构
Modal
|--- / Dialog //核心弹窗组件
|--- / RootNode // 动态节点 移除挂载部分
|--- index.js // 核心弹窗静态方法,函数引用部分,导出
复制代码
代码入口
import React from "react";
import ReactDOM from "react-dom";
import Dialog from "./Dialog/index";
import Modal from "./RootNode/index";
const show = (props) => {
let timer = null;
const div_wrap = document.createElement("div");
document.body.appendChild(div_wrap);
// 移除节点
function removeNode(fn) {
ReactDOM.unmountComponentAtNode(div_wrap);
div_wrap && document.body.removeChild(div_wrap);
if (typeof fn === "function") {
fn();
}
}
const _onCancel = () => {
timer = setTimeout(() => {
removeNode(props.onCancel);
clearTimeout(timer);
}, 350);
};
const _onOk = () => {
timer = setTimeout(() => {
removeNode(props.onOk);
clearTimeout(timer);
}, 350);
};
const _props = {
visible: true,
onOk: _onOk,
onCancel: _onCancel,
mask: true,
maskCloseable: false,
};
ReactDOM.render(
<Dialog {..._props} >
{props.content}
</Dialog>,
div_wrap
);
return null;
};
Modal.success = function success(content) {
common(content, "success");
};
Modal.warning = function warning(content) {
common(content, "warning");
};
Modal.info = function info(content) {
common(content, "info");
};
// 合并参数为对象
function common(content, type) {
let obj = {};
if (typeof content === "object") {
obj = {
...content,
};
} else {
obj = {
content,
_type: type,
};
}
return show(obj);
}
export default Modal;
复制代码
根节点挂载和移除
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import styles from './index.less';
import Dialog from '../Dialog/index';
function Modal(props) {
const { visible } = props;
const [rootNode, setRootNode] = useState(null);
// 监听动画结束的时间,关闭弹窗时,移除dom节点
function aniEnd(endTime) {
return endTime * 1000;
}
// 监听visible,和 根节点rootNode 渲染弹窗
useEffect(() => {
const get_rootNode = document.querySelector('#modal_wrap');
if (visible) {
if (!get_rootNode) {
const modal_wrap = document.createElement('div');
modal_wrap.setAttribute('id', 'modal_wrap');
modal_wrap.setAttribute('class', `${styles.modal_wrap}`);
document.body.appendChild(modal_wrap);
setRootNode(modal_wrap);
} else {
setRootNode(get_rootNode);
}
} else {
// 弹窗消失,等消失动画完成,将根节点清除
if (rootNode) {
const timer = setTimeout(() => {
setRootNode(null);
clearTimeout(timer);
}, 500);
} else {
// 根节点清除后,移除dom
const get_rootNode = document.querySelector('#modal_wrap');
get_rootNode && document.body.removeChild(get_rootNode);
}
}
}, [visible, rootNode]);
return rootNode ? ReactDOM.createPortal(<Dialog {...props} aniEnd={aniEnd} />, rootNode) : null;
}
export default React.memo(Modal);
复制代码
.modal_wrap {
position: absolute;
width: 100vw;
height: 100vh;
left: 0;
top: 0;
}
复制代码
核心弹窗
import React, { useState, useEffect, forwardRef } from 'react';
import classNames from 'classnames';
import styles from './index.less';
function Dialog(props, ref) {
const {
visible,
onOk,
onCancel,
width,
style,
centered,
title,
closable,
closeIcon,
mask,
maskStyle,
maskCloseable,
okText,
// okStyle,
cancelText,
footer,
// _type, // 消息类型
// aniEnd,
} = props;
const modalStyle = {
width: `${width}px`,
...style,
};
const [isOpen, setIsOpen] = useState(false);
const modalClass = classNames({
[`${styles.modal}`]: true,
[`${styles.modalCenter_ani}`]: centered, // 垂直居中
[`${styles.modalShow_ani}`]: centered ? false : true, // 非垂直居中
[`${styles.modalHidden_ani}`]: !isOpen && !centered, // 非垂直居中 消失
[`${styles.modalHidden_center_ani}`]: !isOpen && centered, // 垂直居中 消失
});
const maskClass = classNames({
[`${styles.mask}`]: true,
[`${styles.mask_hidden_ani}`]: !isOpen,
});
const closeBtn = (
<span className={styles.close} onClick={handle_cancel}>
X
</span>
);
const _closeIcon = closeIcon
? React.cloneElement(closeIcon, {
...closeIcon.props,
onClick: handle_cancel,
})
: closeBtn;
function handle_ok() {
setIsOpen(false);
onOk && onOk();
}
function handle_cancel() {
setIsOpen(false);
onCancel && onCancel();
}
// 监控动画结束的回调
function watchAniEnd(e) {
// console.log(e);
// typeof aniEnd === "function" && aniEnd(e.elapsedTime);
}
useEffect(() => {
setIsOpen(visible);
}, [visible]);
return (
<>
{mask ? (
<div
onClick={() => {
maskCloseable && onCancel();
}}
style={maskStyle}
className={maskClass}
ref={ref}
onAnimationEnd={watchAniEnd}
></div>
) : null}
<div style={modalStyle} className={modalClass}>
{title ? <div className={styles.title}>{title}</div> : null}
<div className={styles.closeIcon}>{closable ? _closeIcon : null}</div>
<div className={styles.body}>{props.children}</div>
<div className={styles.footer}>
{footer === null ? null : React.isValidElement(footer) ? (
footer
) : (
<div className={styles.footer_btn}>
<button className={styles.btn_ok} onClick={handle_ok}>
{okText}
</button>
<button className={styles.btn_cancel} onClick={handle_cancel}>
{cancelText}
</button>
</div>
)}
</div>
</div>
</>
);
}
export default forwardRef(Dialog);
Dialog.defaultProps = {
centered: false,
closable: false,
mask: true,
maskCloseable: 'false',
okText: '确认',
cancelText: '取消',
};
复制代码
.mask {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
}
.mask_hidden_ani {
animation: maskHidden 0.4s forwards;
}
.modal {
position: absolute;
left: 50%;
top: -50%;
transform: translateX(-50%);
width: 500px;
height: auto;
border-radius: 4px;
display: block;
z-index: 1001;
// background-color: #ccc;
padding:24px;
background: rgb(32, 34, 41);
transform-origin: 0 0;
transition: all 1s;
}
.modalShow_ani {
transform-origin: 0 0;
animation: boxShow 0.3s forwards;
}
.modalHidden_ani {
animation: boxHidden 0.3s forwards;
}
.modalCenter_ani {
top: 50%;
transform: translate(-50%, -50%);
animation: boxShow_center 0.3s forwards;
}
.modalHidden_center_ani{
animation: boxHidden_center 0.3s forwards;
}
.visible {
display: block;
}
.title {
height: 30px;
line-height: 30px;
color:#fff;
margin-top: 8px;
font-size: 18px;
// border-bottom: 1px solid #fff;
}
.closeIcon {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
.close {
position: absolute;
top: 10px;
right: 10px;
width: 20px;
height: 20px;
line-height: 20px;
padding: 5px;
text-align: center;
cursor: pointer;
color:#fff;
}
.body{
color:#fff;
min-height: 84px;
font-size: 14px;
padding-top: 8px;
}
.footer {
width: 100%;
min-height: 32px;
// padding: 0 24px 24px 24px;
button {
outline: none;
border:none;
float: right;
cursor: pointer;
color: #fff;
}
}
.footer_btn {
// border-top: 1px solid #fff;
width: 100%;
height: 100%;
color:#fff;
.btn_ok {
margin-left: 16px;
width: 96px;
height: 32px;
line-height: 32px;
border-radius: 2px;
text-align: center;
background-color: #3e7dff;
&:hover {
background: #709fff;
}
}
.btn_cancel {
height: 32px;
width: 96px;
line-height: 32px;
border-radius: 2px;
text-align: center;
background-color: #2a2d35;
&:hover {
background: #414554;
}
}
}
@keyframes boxShow {
from {
top: -20%;
opacity: 1;
}
to {
top: 20%;
opacity: 1;
transform: translateX(-50%);
}
}
@keyframes boxShow_center {
from {
top: -20%;
opacity: 0;
}
to {
top: 50%;
opacity: 1;
}
}
@keyframes boxHidden {
0% {
opacity: 1;
top:20%;
}
50%{
opacity: 0;
}
100% {
opacity: 0;
top:-20%;
// transform: scale(0.2);
}
}
@keyframes boxHidden_center {
0% {
opacity: 1;
top:50%;
// left: 50%;
}
50%{
opacity: 0;
}
100% {
opacity: 0;
top:-20%;
// left: 50%;
// transform: scale(0.2);
}
}
@keyframes maskHidden {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
复制代码
里边用了很多定时器。还有需要改进的地方。请各位指正。peace.
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END