当前位置: 首页 > news >正文

react+antd 可拖拽模态框组件

DraggableModal 可拖拽模态框组件使用说明

概述

DraggableModal 是一个基于 @dnd-kit/core 实现的可拖拽模态框组件,允许用户通过拖拽标题栏来移动模态框位置。该组件具有智能边界检测功能,确保模态框始终保持在可视区域内。

功能特性

  • 可拖拽移动:支持通过鼠标拖拽移动模态框位置
  • 智能边界检测:防止模态框被拖拽到屏幕可视区域外
  • 响应式适配:根据窗口大小自动调整可拖拽范围
  • 平滑交互:使用 CSS transform 实现流畅的拖拽动画
  • 类型安全:完整的 TypeScript 类型支持

安装依赖

确保项目中已安装以下依赖:

npm install @dnd-kit/core @dnd-kit/utilities
# 或
yarn add @dnd-kit/core @dnd-kit/utilities

基本用法

1. 导入组件

import DraggableModal from './components/DraggableModal';

2. 基础示例

import React, { useState } from 'react';
import { Modal } from 'antd';
import DraggableModal from './components/DraggableModal';const ExampleComponent: React.FC = () => {const [visible, setVisible] = useState(false);return (<><button onClick={() => setVisible(true)}>打开可拖拽模态框</button><Modaltitle="可拖拽的模态框"open={visible}onCancel={() => setVisible(false)}modalRender={(modal) => (<DraggableModal>{modal}</DraggableModal>)}><p>这是一个可以拖拽的模态框内容</p></Modal></>);
};export default ExampleComponent;

3. 与 Antd Modal 结合使用

import React, { useState } from 'react';
import { Modal, Form, Input, Button } from 'antd';
import DraggableModal from '@/pages/StdFormEdit/components/DraggableModal';const FormModal: React.FC = () => {const [visible, setVisible] = useState(false);const [form] = Form.useForm();const handleSubmit = async () => {try {const values = await form.validateFields();console.log('表单数据:', values);setVisible(false);} catch (error) {console.error('表单验证失败:', error);}};return (<><Button type="primary" onClick={() => setVisible(true)}>打开表单模态框</Button><Modaltitle="用户信息编辑"open={visible}onCancel={() => setVisible(false)}footer={[<Button key="cancel" onClick={() => setVisible(false)}>取消</Button>,<Button key="submit" type="primary" onClick={handleSubmit}>确定</Button>]}modalRender={(modal) => (<DraggableModal>{modal}</DraggableModal>)}><table ...></Modal></>);
};export default FormModal;

API 说明

DraggableModal Props

属性类型默认值说明
childrenReactNode-需要包装的模态框内容

组件内部实现细节

DraggableWrapper Props
属性类型说明
topnumber模态框垂直位置偏移量
leftnumber模态框水平位置偏移量
childrenReactNode子组件内容
modalRefRefObject<HTMLDivElement>模态框DOM引用

技术实现

核心特性

  1. 拖拽识别:自动识别具有 modal-header 类名的元素作为拖拽手柄
  2. 边界限制
    • 垂直方向:上边界 -100px,下边界为窗口高度减去模态框高度再减去100px
    • 水平方向:限制在窗口宽度范围内,保持模态框居中对称
  3. 位置计算:使用 CSS transform 属性实现位置变换,性能优异

边界检测算法

// 垂直边界检测
const needRemoveMinHeight = -100; // 上边界
const needRemoveMaxHeight = winH - 100 - modalRef.current.clientHeight; // 下边界// 水平边界检测  
const needRemoveWidth = (winW - modalRef.current.clientWidth) / 2; // 左右对称边界

使用注意事项

1. 模态框结构要求

确保被包装的模态框包含具有 modal-header 类名的标题栏元素:

// ✅ 正确 - Antd Modal 自动包含 modal-header 类名
<Modal title="标题">内容</Modal>// ❌ 错误 - 自定义模态框缺少 modal-header 类名
<div className="custom-modal"><div className="title">标题</div> {/* 缺少 modal-header 类名 */}<div>内容</div>
</div>

2. 性能优化建议

  • 避免在 DraggableModal 内部频繁更新状态
  • 对于复杂内容,建议使用 React.memo 优化子组件渲染
const OptimizedContent = React.memo(() => {return (<div>{/* 复杂内容 */}</div>);
});<DraggableModal><Modal title="优化示例"><OptimizedContent /></Modal>
</DraggableModal>

DraggableModal源码

import React, { useState, useRef, useLayoutEffect } from 'react';
import { DndContext, useDraggable } from '@dnd-kit/core';
import type { Coordinates } from '@dnd-kit/utilities';const DraggableWrapper = (props: any) => {const { top, left, children: node, modalRef } = props;const { attributes, isDragging, listeners, setNodeRef, transform } = useDraggable({id: 'draggable-title'});const dragChildren = React.Children.map(node.props.children, (child) => {if (!child) {return child;}if (child.type === 'div' && child.props?.className?.indexOf('modal-header') >= 0) {return React.cloneElement(child, {'data-cypress': 'draggable-handle',style: { cursor: 'move' },...listeners});}return child;});let offsetX = left;let offsetY = top;if (isDragging) {offsetX = left + (transform?.x ?? 0);offsetY = top + transform?.y;}return (<divref={(el) => {setNodeRef(el);if (modalRef) modalRef.current = el;}}{...attributes}style={{transform: `translate(${offsetX ?? 0}px, ${offsetY ?? 0}px)`} as React.CSSProperties}>{React.cloneElement(node, {}, dragChildren)}</div>);
};const DraggableModal = (props: any) => {const [{ x, y }, setCoordinates] = useState<Coordinates>({x: 0,y: 0});const modalRef = useRef<HTMLDivElement>(null);const [modalSize, setModalSize] = useState({ width: 0, height: 0 });useLayoutEffect(() => {if (modalRef.current) {const rect = modalRef.current.getBoundingClientRect();setModalSize({ width: rect.width, height: rect.height });}}, [props.children]);return (<DndContextonDragEnd={({ delta }) => {const winW = window.innerWidth;const winH = window.innerHeight;const needRemoveMinHeight = -100;const needRemoveWidth = (winW - modalRef.current.clientWidth) / 2;const needRemoveMaxHeight = winH - 100 - modalRef.current.clientHeight;const newX = x + delta.x;const newY = y + delta.y;let curNewY = newY;if (newY < 0) {curNewY = newY < needRemoveMinHeight ? needRemoveMinHeight : newY;} else {curNewY = newY > needRemoveMaxHeight ? needRemoveMaxHeight : newY;}if (Math.abs(newX) < needRemoveWidth) {setCoordinates({x: newX,y: curNewY});} else {setCoordinates({x: newX < 0 ? 0 - needRemoveWidth : needRemoveWidth,y: curNewY});}}}><DraggableWrapper top={y} left={x} modalRef={modalRef}>{props.children}</DraggableWrapper></DndContext>);
};export default DraggableModal;

3. 兼容性说明

  • 支持现代浏览器(Chrome 88+、Firefox 84+、Safari 14+)
  • 移动端暂不支持拖拽功能
  • 需要 React 16.8+ 版本支持

故障排除

常见问题

Q: 模态框无法拖拽?

A: 检查以下几点:

  1. 确保模态框标题栏包含 modal-header 类名
  2. 确认 @dnd-kit/core 依赖已正确安装
  3. 检查是否有其他元素阻止了拖拽事件

Q: 拖拽时出现跳跃现象?

A: 这通常是由于 CSS 样式冲突导致,确保没有其他 transform 样式影响模态框定位。

Q: 模态框被拖拽到屏幕外?

A: 组件内置了边界检测,如果出现此问题,请检查窗口大小变化时是否正确触发了重新计算。

版本历史

  • v1.0.0: 初始版本,支持基础拖拽功能和边界检测
http://www.lryc.cn/news/593486.html

相关文章:

  • Python之--元组
  • RabbitMQ01——基础概念、docker配置rabbitmq、内部执行流程、五种消息类型、测试第一种消息类型
  • Java学习--------消息队列的重复消费、消失与顺序性的深度解析​
  • I/O 多路复用select,poll
  • Java 中的继承与多态
  • 5.组合模式
  • 3.5软件开发活动[2-系统设计]面向对象设计-UML统一开发过程
  • [故障诊断方向]SNNs:针对小样本轴承故障诊断的孪生神经网络模型
  • 在Vscode中使用Kimi K2模型:实践指南,三分钟生成个小游戏
  • 练习三:熟知前端知识
  • 目标检测中的标签分配算法总结
  • MinIO深度解析:从核心特性到Spring Boot实战集成
  • Vue的路由模式的区别和原理
  • 《Qt5串口开发》搭建跨平台通信系统
  • VSCode用Python操作MySQL:环境配置与代码验证
  • 操作系统-分布式同步
  • 实验室危险品智能管控:行为识别算法降低爆炸风险
  • Mybatis学习之简介(一)
  • Vue 3 中封装并使用 IndexedDB 的完整教程(含泛型、模块化、通用 CRUD)
  • Vue Swiper组件
  • 93.数字信号处理相关的一些问题
  • 单元测试学习+AI辅助单测
  • 【ArcGIS技巧】最近分享的GIS插件总结与优化
  • Spring MVC源码分析 DispatcherServlet#getHandlerAdapter方法
  • LVS四种工作模式深度解析
  • Go 语言核心机制深度剖析:指针、defer、多态与空接口实战指南
  • 使用 go-redis-entraid 实现 Entra ID 无密钥认证
  • Go-Redis × RediSearch 全流程实践
  • leetcode_121 买卖股票的最佳时期
  • 力扣经典算法篇-26-长度最小的子数组(暴力求解法,左右指针法)