这次我们来实现一个常用的 Drawer 组件,this带你来体验体验, Go, 话不多说,先放图片
Drawer 组件实现,基础属性比较简单,getContainer 的实现需要用到 react 的 Portals api 如果对Portal 还不了解的, 可以先看下官方文档 zh-hans.reactjs.org/docs/portal…
通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点
使用
ReactDOM.createPortal(
this.props.children,
domNode // 要挂载的元素
);
然后就来实现我们的Drawer 组件
/**
* @param {visible} 控制Drawer显示
* onClose 关闭回调
* title 标题
* width 宽度
* zIndex zIndex
* placement 抽屉方向
* mask 是否展示遮罩
* maskClosable 点击遮罩是否关闭
* closable 是否显示右上角关闭按钮
* destroyOnClose 关闭抽屉销毁子元素
* getContainer 指定 Drawer 挂载的 HTML 节点, 可以将抽屉挂载在任何元素上
* drawerStyle 能自定义抽屉弹出层样式
*/
import { useState, useEffect } from 'react';
import styles from './index.less';
import ReactDOM from 'react-dom';
import classnames from 'classnames';
interface IpropsDrawer {
visible: boolean;
title: React.ReactNode;
width: number;
onClose: () => void;
zIndex: number;
placement: string;
mask: boolean;
maskClosable: boolean;
closable: boolean;
destroyOnClose: boolean;
getContainer: HTMLElement | false;
drawerStyle: any;
}
type Iprops = IpropsDrawer;
export const Drawer: React.FC<Partial<Iprops>> = (props) => {
const {
visible,
title = '标题',
closable = true,
width = 300,
onClose,
zIndex = 1000,
placement = 'right',
mask = true,
maskClosable = true,
destroyOnClose = true,
getContainer = document.body,
drawerStyle,
} = props;
// 控制关闭弹框清空弹框里面的元素
const [clearContentDom, setClearContentDom] = useState(false);
// 控制drawer 的显示隐藏
const [drawerVisible, setDrawerVisible] = useState(visible);
useEffect(() => {
setDrawerVisible(() => {
if (getContainer !== false && visible) {
getContainer.style.overflow = 'hidden';
}
return visible;
});
if (visible) {
setClearContentDom(false);
}
}, [visible]);
// 点击弹框关闭
const handleClose = () => {
setDrawerVisible((prev) => {
if (getContainer !== false && prev) {
getContainer.style.overflow = 'auto';
}
return false;
});
onClose && onClose();
if (destroyOnClose) {
setClearContentDom(true);
}
};
const drawerDom = (
<div
className={styles.drawerWarp}
style={{
width: drawerVisible ? '100%' : '0',
zIndex,
position: getContainer === false ? 'absolute' : 'fixed',
}}
>
{mask && (
<div
className={styles.drawerMask}
style={{ opacity: drawerVisible ? 1 : 0 }}
onClick={maskClosable ? handleClose : undefined}
></div>
)}
<div
className={classnames(styles.drawerContent, !drawerVisible ? styles.closeDrawer : '')}
style={{
width,
[placement]: 0,
...drawerStyle,
}}
>
{title && <div className={styles.titleDrawer}>{title}</div>}
<div style={{ padding: 16 }}>{clearContentDom ? null : props.children}</div>
{closable && (
<span className={styles.closeDrawerBtn} onClick={handleClose}>
X
</span>
)}
</div>
</div>
);
// getContainer 默认你是body, 如果为 false 则挂在最近的父节点上, 如果自己传入一个要
// 挂在的对象下,则需要用到 ReactDOM.createPortal
return getContainer === false && !getContainer
? drawerDom
: ReactDOM.createPortal(drawerDom, getContainer);
};
添加对应的样式
.drawerWarp {
position: absolute;
top: 0;
height: 100vh;
overflow: hidden;
.drawerMask {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
transition: all 0.3s;
}
.drawerContent {
position: absolute;
top: 0;
height: 100%;
// padding: 16px;
background-color: #fff;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
transition: all 0.3s;
.titleDrawer {
padding: 16px;
color: #000000d9;
font-weight: 500;
font-size: 16px;
border-bottom: 1px solid #f0f0f0;
}
.closeDrawerBtn {
position: absolute;
top: 16px;
right: 16px;
padding-left: 16px;
color: #ccc;
font-size: 16px;
cursor: pointer;
}
}
.closeDrawer {
transform: translateX(100%);
}
}
来使用一下
const [open2, setOpen2] = useState(false);
<Drawer visible={open2} onClose={() => setOpen2(false)}>
输入
<Input />
</Drawer>
大功告成 !!! 如果对你有帮助,就请点个赞吧👍