写在前面:
在移动端展示数据时,常常会用到popup组件展示,本文实现一个可以上下拖动并且超出视口会自动回弹的popup组件
效果如下:
实现如下:
安装依赖库
首先引入antd
、react-draggable
来。
封装步骤:
-
定义
Popup
组件。这是一个函数式组件,接受onClose
和widget
作为参数。在组件内部,通过调用useState
钩子函数创建了一个名为position
的状态变量,并初始化{ x: 0, y: 0 }
。position
用于跟踪弹出窗口的位置信息。 -
定义一个
handleDrag
函数,用于处理拖拽事件。当拖拽事件发生时,该函数会根据拖拽的位置更新position
的状态。同时,通过一些条件判断限制了弹出窗口的垂直拖拽范围。 -
使用
StyledPopup
组件作为根容器,并嵌套了其他子元素。其中,使用了Draggable
组件实现了弹出窗口的可拖拽功能,handle=".handle-area"
表示只能通过拖拽.handle-area
类的元素来进行拖拽。在拖拽过程中,会触发handleDrag
函数来更新position
的状态。
代码如下:
tsx
import { Table } from 'antd';
import React, { useState } from 'react';
import Draggable from 'react-draggable';
import styled from 'styled-components';
import closeSvg from '../../../../assets/images/close.svg';
import { Widget } from '../../types/widgetTypes';
interface PopupProps {
onClose: () => void;
widget: Widget | undefined;
}
const Popup: React.FC<PopupProps> = ({ onClose, widget }) => {
const closePopup = () => {
onClose();
};
const tableData = widget?.config?.content?.dataChart?.config?.sampleData;
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleDrag = (e, ui) => {
if (ui.y < 0 && ui.y < -window.innerHeight * 0.48) {
ui.y = -window.innerHeight * 0.48;
} else if (ui.y > 0 && ui.y > window.innerHeight * 0.45) {
ui.y = window.innerHeight * 0.45;
}
setPosition({ x: 0, y: ui.y });
};
return (
<StyledPopup>
<Draggable
handle=".handle-area"
axis="y"
onDrag={handleDrag}
position={position}
>
<div className="popup">
<div className="handle-area">
<div className="popup-handle"></div>
</div>
<div className="popup-header">
<span>查看数据</span>
<img onClick={closePopup} src={closeSvg} alt="Close" />
</div>
<div className="popup-content">
{
<TableWrapper>
<Table
size="small"
dataSource={tableData?.rows}
columns={tableData?.columns?.map((col, index) => ({
key: col.name,
title: col.name,
dataIndex: index,
}))}
bordered
/>
</TableWrapper>
}
</div>
</div>
</Draggable>
</StyledPopup>
);
};
const TableWrapper = styled.div`
padding: 10px;
`;
const StyledPopup = styled.div`
position: fixed;
top: 50%;
width: 100vw;
& .popup {
display: flex;
flex-direction: column;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
height: 150vh;
background-color: #fff;
.popup-handle {
height: 5px;
width: 10%;
background-color: rgb(217, 217, 217);
cursor: row-resize;
border-radius: 5px;
margin: 8px auto;
margin-bottom: 16px;
}
.popup-header {
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
font-size: 20px;
font-weight: 700;
color: rgb(19, 23, 55);
img {
position: absolute;
right: 5px;
top: 50%;
transform: translate(-50%, -50%);
width: 36px;
height: 36px;
}
}
}
.popup-content {
display: flex;
flex-direction: column;
margin: 20px 15px;
max-height: 80vh;
overflow: auto;
}
`;
export default Popup;
3.使用参考:
比如可以在打开组件的函数、tsx内,传入数据以及打开的boolen值,并暴露一个onclose函数实现关闭popup的解构
tsx
const [popupOpen, setPopupOpen] = useState(false);
const [popupWidget, setPopupWidget] = useState<Widget>();
const onClosePopup = () => {
setPopupOpen(false);
};
onWidgetOpenPopup: (widget: Widget) => {
setPopupOpen(true);
setPopupWidget(widget);
},
return (
...
{popupOpen && <Popup onClose={onClosePopup} widget={popupWidget} />
...}
);