实现可拖拽的 Ant Design Modal 并保持下层 HTML 可操作性

打印 上一主题 下一主题

主题 900|帖子 900|积分 2700

前言

在开发复杂的前端界面时,我们经常需要一个可拖拽的弹窗(Modal),同时又希望用户能够在弹窗打开的情况下操作下层的内容。Ant Design 的 Modal 组件提供了强盛的功能,但默认情况下,弹窗会覆盖整个页面,阻止用户与下层内容交互。本文将通过代码示例,展示怎样实现一个可拖拽的 Modal,并确保弹窗不会影响下层 HTML 的操作
效果图


视频演示


     可拖拽antd Modal和保持下层 HTML 可操作性
  
技能栈

• React: 前端框架
• Ant Design: UI 组件库
• react-draggable: 可拖拽组件库
需求分析


  • 可拖拽的 Modal: 用户可以通过拖拽 Modal 的标题栏来移动弹窗位置。
  • 下层内容可操作: 弹窗打开时,用户仍然可以操作下层的 HTML 元素。
  • 响应式拖拽限定: 弹窗的拖拽范围应限定在可视地域内。
    实现思路
  • 使用 react-draggable 包装 Modal: 利用 react-draggable 提供的拖拽功能,将 Modal 包装为可拖拽组件。
  • 自定义 Modal 的遮罩层: 通过设置遮罩层的 pointerEventsbackgroundColor,使其完全透明且不阻止鼠标事件。
  • 动态盘算拖拽边界: 根据窗口大小和 Modal 的尺寸,动态盘算拖拽的边界,确保 Modal 不会超出可视地域。
代码实现

  1. import { Button, Modal, Input } from 'antd';
  2. import React, { useRef, useState } from 'react';
  3. import type { DraggableData, DraggableEvent } from 'react-draggable';
  4. import Draggable from 'react-draggable';
  5. const text = `...`; // 省略的 Lorem Ipsum 文本
  6. const App: React.FC = () => {
  7.   const [open, setOpen] = useState(false);
  8.   const [disabled, setDisabled] = useState(false);
  9.   const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
  10.   const draggleRef = useRef<HTMLDivElement>(null);
  11.   const showModal = () => {
  12.     setOpen(true);
  13.   };
  14.   const handleOk = (e: React.MouseEvent<HTMLElement>) => {
  15.     console.log(e);
  16.     setOpen(false);
  17.   };
  18.   const handleCancel = (e: React.MouseEvent<HTMLElement>) => {
  19.     console.log(e);
  20.     setOpen(false);
  21.   };
  22.   const onStart = (_event: DraggableEvent, uiData: DraggableData) => {
  23.     const { clientWidth, clientHeight } = window.document.documentElement;
  24.     const targetRect = draggleRef.current?.getBoundingClientRect();
  25.     if (!targetRect) {
  26.       return;
  27.     }
  28.     setBounds({
  29.       left: -targetRect.left + uiData.x,
  30.       right: clientWidth - (targetRect.right - uiData.x),
  31.       top: -targetRect.top + uiData.y,
  32.       bottom: clientHeight - (targetRect.bottom - uiData.y),
  33.     });
  34.   };
  35.   return (
  36.     <>
  37.       <Button type="primary" onClick={showModal}>Open Draggable Modal</Button>
  38.       <div>
  39.         <Input placeholder="Input here" />
  40.         <p>{text}</p>
  41.         <p>{text}</p>
  42.         <p>{text}</p>
  43.         <p>{text}</p>
  44.       </div>
  45.       <Modal
  46.         title={
  47.           <div
  48.             style={{
  49.               width: '100%',
  50.               cursor: 'move',
  51.             }}
  52.             onMouseOver={() => {
  53.               if (disabled) {
  54.                 setDisabled(false);
  55.               }
  56.             }}
  57.             onMouseOut={() => {
  58.               setDisabled(true);
  59.             }}
  60.           >
  61.             Draggable Modal
  62.           </div>
  63.         }
  64.         open={open}
  65.         onOk={handleOk}
  66.         onCancel={handleCancel}
  67.         maskStyle={{
  68.           pointerEvents: 'none', // 禁用遮罩层的交互,允许下层内容被操作
  69.           backgroundColor: 'rgba(0, 0, 0, 0)', // 使遮罩层完全透明
  70.         }}
  71.         modalRender={modal => (
  72.           <Draggable
  73.             disabled={disabled}
  74.             bounds={bounds}
  75.             onStart={(event, uiData) => onStart(event, uiData)}
  76.           >
  77.             <div ref={draggleRef} style={{ position: 'relative', zIndex: 1000 }}>
  78.               {modal}
  79.             </div>
  80.           </Draggable>
  81.         )}
  82.         bodyStyle={{ overflow: 'hidden' }} // 防止 Modal 内容滚动
  83.         style={{ pointerEvents: 'auto' }} // 确保 Modal 可以正常交互
  84.         getContainer={false} // 防止 Modal 被附加到默认的 body 中
  85.         wrapClassName="modal-no-mask" // 自定义样式类
  86.       >
  87.         <p>
  88.           Just don't learn physics at school and your life will be full of magic and miracles.
  89.         </p>
  90.         <br />
  91.         <p>Day before yesterday I saw a rabbit, and yesterday a deer, and today, you.</p>
  92.       </Modal>
  93.       <style>
  94.         {`
  95.           .modal-no-mask {
  96.             pointer-events: none; /* 确保 Modal 不阻止鼠标事件 */
  97.           }
  98.           .modal-no-mask .ant-modal-content {
  99.             pointer-events: auto; /* 确保 Modal 内容可以交互 */
  100.           }
  101.         `}
  102.       </style>
  103.     </>
  104.   );
  105. };
  106. export default App;
复制代码
关键点解析


  • react-draggable 的使用
    Draggable 组件通过 bounds 属性限定拖拽范围,确保 Modal 不会超出可视地域。
    onStart 回调函数用于动态盘算拖拽边界。
  • 自定义遮罩层
    • 通过设置 maskStylepointerEventsnonebackgroundColor 为透明,使遮罩层不会阻止鼠标事件,同时保持完全透明。
  • Modal 的可交互性
    • 设置 style={{ pointerEvents: ‘auto’ }} 确保 Modal 内容可以正常交互。
    • 使用 getContainer={false} 防止 Modal 被附加到默认的 body 中,制止样式冲突。
  • 动态盘算拖拽边界
    • 在 onStart 回调中,根据窗口大小和 Modal 的尺寸动态盘算拖拽边界,确保 Modal 的拖拽范围始终在可视地域内。
    效果展示
  • 可拖拽的 Modal: 用户可以通过拖拽标题栏移动 Modal 的位置。
  • 下层内容可操作: 弹窗打开时,用户仍然可以操作下层的输入框和其他内容。
  • 响应式拖拽限定: Modal 的拖拽范围始终在可视地域内,不会超出屏幕边界。
总结

通过结合 react-draggableAnt DesignModal 组件,我们实现了一个可拖拽的弹窗,并确保弹窗不会影响下层 HTML 的操作。这种实现方式在复杂的前端界面中非常实用,比方在地图应用、图表展示或多任务操作场景中,用户可以在弹窗打开的情况下继续操作下层内容。
希望本文的实现思路和代码示例能为你的项目提供帮助!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

商道如狼道

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表