思路
可以看看抖音渡一袁老师
,封装一个函数showModal
传入一个options
,类似于antd
的modal
。
- 创建一个外层容器
fragment
,把它挂载到body
上 - 利用
jsx
写个组件,把它挂载到fragment
上 - 点击关闭时,则先卸载组件,再移除
fragment
jsx
中的css
与普通单文件组件不一样,可以使用css module
,css in js
,style component
,react
怎么用,这里也可以怎么用。- 这里的
children
我设计可以传入string
,number
,() => VNode
效果
实现
- 目录结构
- jsx
import { createApp, ref, h, type VNode } from 'vue'
import style from './modal.module.css'
interface options {
title?: string
maskClosable?: boolean
children?: string | number | (() => VNode)
onClose: (close: () => void) => void
}
function showModal(options: options) {
const defaultOptions = ref<options>({
title: 'Basic Modal',
maskClosable: true,
children: () => h('p', 'some content...'),
onClose: (close) => {
close()
}
})
const mergeOptions = Object.assign(defaultOptions.value, options)
const fragment = document.createElement('div')
fragment.className = style.wrap
document.body.appendChild(fragment)
const app = createApp(
{
render() {
return (
<div class={style.container}>
<div class={style.content}>
<h1>{mergeOptions.title}</h1>
<svg
class={style.closeIcon}
width="1em"
height="1em"
onClick={() => {
mergeOptions.onClose(close)
}}
>
<line x1="0" y1="0" x2="1em" y2="1em" stroke="#242424"></line>
<line x1="0" y1="1em" x2="1em" y2="0" stroke="#242424"></line>
</svg>
{typeof mergeOptions.children === 'function'
? mergeOptions.children!()
: mergeOptions.children}
<button
class={style.closeBtn}
onClick={() => {
mergeOptions.onClose(close)
}}
>
关闭
</button>
</div>
</div>
)
}
},
{
mergeOptions,
onclick() {
mergeOptions.maskClosable && mergeOptions.onClose(close)
}
}
)
app.mount(fragment)
const close = () => {
app.unmount()
fragment.remove()
}
window.addEventListener('unload', () => {
mergeOptions.onClose(close)
})
}
export default showModal
- css
.wrap {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.625);
z-index: 9999;
overflow: hidden;
}
.wrap .container {
position: relative;
width: 100%;
height: 100%;
}
.wrap .container .closeIcon {
position: absolute;
top: 20px;
right: 24px;
}
.wrap .container .closeIcon:hover line {
stroke: #e92f20;
}
.wrap .container .closeBtn {
display: block;
margin-left: auto;
margin-top: 20px;
outline: none;
font-weight: 400;
white-space: nowrap;
text-align: center;
border: 1px solid transparent;
cursor: pointer;
color: #fff;
font-size: 14px;
height: 32px;
padding: 4px 15px;
border-radius: 6px;
background-color: #1677ff;
box-shadow: 0 2px 0 rgba(5, 145, 255, 0.1)
}
.wrap .container .content {
position: absolute;
top: 10%;
left: 50%;
transform: translate(-50%);
width: 520px;
background-color: #ffffff;
background-clip: padding-box;
border: 0;
border-radius: 8px;
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
pointer-events: auto;
padding: 20px 24px;
}