TS+Hooks二次封装antd Modal,实现可拖拽


一、最终效果图

使用Typescript和React Hooks二次封装antd的Modal对话框,实现可拖拽效果,并且满足下面的小细节。最终效果图如下:

小细节:

  1. 拖动时鼠标于DIV的相对位置
  2. 拖动时选中文字
  3. 边界问题
  4. 页面失去焦点
  5. 销毁时回到原位
    请添加图片描述

二、可拖动DIV

在实现Modal对话框拖拽的之前,可以在html页面上先实现一个可拖动的DIV,我是学习了这篇文章可拖动DIV。里面的具体原理细节可以参考这篇文章。

请添加图片描述直接通过图的方式表达可能更加直观。主要就是要确定好相对移动距离和边界条件。我的在线测试DEMO

请添加图片描述

三、封装ModalBox

1. 实现原理

  1. 有部分参考了这篇文章,antd Modal的class属性是ant-modal-content,通过这个属性,获得Modal对话框具体左右视图边框的距离contentLeft和contentRight,用于后续的定位。
    在这里插入图片描述
    在这里插入图片描述
  2. 相对移动位置
var cx = e.clientX - startPosX + styleLeft,
    cy = e.clientY - startPosY + styleTop;
  1. Modal对话框位置再次点击更新
    这里的132就是Modal高度的一半,为了让他销毁后每次都保持在视图中央。如果自己改变宽高度的话,可以再次封装style中的width,就不至于写死了。因为Modal组件关闭的时候,是不会自动销毁的,如果不写下面的代码,会导致再次点开Modal后,还是上次那个位置。这个地方写的不太好,继续优化。
    useEffect(() => {
        return () => {
            setStyleLT({
                styleLeft: 0,
                styleTop: window.innerHeight / 2 - 132
            })
            setFlag(true);
        }
    }, [props.isModalVisible])

2.ModalBox 组件代码

import { Modal } from 'antd';
import { useState, useEffect } from 'react';

interface ModalBoxxProps {
    title: string,
    isModalVisible: boolean,
    handleOk: any,
    handleCancel: any,
    children?: React.ReactNode,
    okText?: string,
    cancelText?: string
}
let content: HTMLDivElement, contentLeft: number = 0, contentRight: number = 0;

const ModalBox: React.FC<ModalBoxxProps> = (props) => {
    const [styleLT, setStyleLT] = useState({
        styleLeft: 0, styleTop: window.innerHeight / 2 - 132
    });
    let [flag, setFlag] = useState<boolean>(true);
    const style = { left: styleLT.styleLeft, top: styleLT.styleTop }

    useEffect(() => {
        return () => {
            setStyleLT({
                styleLeft: 0,
                styleTop: window.innerHeight / 2 - 132
            })
            setFlag(true);
        }
    }, [props.isModalVisible])

    const onMouseDown = (e: any) => {
        e.preventDefault();
        if (flag) {
            content = document.getElementsByClassName("ant-modal-content")[0] as HTMLDivElement;
            contentLeft = content.getBoundingClientRect().left;
            contentRight = content.getBoundingClientRect().right - content.offsetWidth;
            setFlag(false);
        }

        // 记录初始移动的鼠标位置
        const startPosX = e.clientX
        const startPosY = e.clientY;
        const { styleLeft, styleTop } = styleLT
        // 添加鼠标移动事件
        document.onmousemove = (e) => {
            var cx = e.clientX - startPosX + styleLeft,
                cy = e.clientY - startPosY + styleTop;
                
            if (cx < -contentLeft) {
                cx = -contentLeft;
            }
            if (cy < 0) {
                cy = 0;
            }
            if (cx > contentRight) {
                cx = contentRight;
            }

            if (window.innerHeight - cy < content.offsetHeight) {
                cy = window.innerHeight - content.offsetHeight;
            }
            setStyleLT({
                styleLeft: cx,
                styleTop: cy
            })
        } 
        // 鼠标放开时去掉移动事件
        document.onmouseup = function (e) {
            document.onmousemove = null
            if (e.clientX > window.innerWidth || e.clientY < 0 || e.clientX < 0 || e.clientY > window.innerHeight) {
                document.onmousemove = null
            }
        }
    }

    return (
        <Modal
            title={<div
                className='dragBoxBar'
                style={{ height: "100", width: "100%", cursor: 'move', userSelect: "none" }}
                onMouseDown={onMouseDown}
            >
                {props.title}
            </div>}
            visible={props.isModalVisible}
            onOk={props.handleOk}
            onCancel={props.handleCancel}
            style={style}
            wrapClassName='dragBox'
            okText={props.okText}
            cancelText={props.cancelText}
        >
            {props.children}
        </Modal>
    )
}

export default ModalBox;

4. ModalBox组件调用

import { Button } from "antd";
import { useState } from "react";
// 引入组件
import { ModalBox } from "../Components";

interface ListProps {
    name?: string,
    age?: number
}
const List = (props: ListProps) => {
    const [showModal, setShowModal] = useState<boolean>(false)
    const handleModal = () => {
        setShowModal(true);
    }
    const handleOk = () => {
        setShowModal(false);
    };
    const handleCancel = () => {
        setShowModal(false);
    };
    return (
        <>
            <div>列表页组件</div>
            <ModalBox
                title="告警信息"
                isModalVisible={showModal}
                handleOk={handleOk}
                handleCancel={handleCancel}
                okText="确定"
                cancelText="取消"
            >
                <p>告警</p>
                <p>告警</p>
                <p>告警</p>
            </ModalBox>
            <Button onClick={handleModal}>点击弹窗</Button>
        </>
    )
}
export default List;

四、总结

Modal每次消失的时候,不太流畅,需要再次优化,如果有好的建议,希望能和我一起交流。(* ̄︶ ̄)

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值