改造antd的Modal組件使其可拖拽

最近幾個月在作一個react的項目,項目作到了百分之八十,而後業務要求項目裏面的模態框能夠拖拽,呵呵,早不提,晚不提,恰恰如今提,儘管我內心罵了一萬遍,但是仍是隻能老老實實搞啊,畢竟誰叫人家是上帝,吾等凡人只能任其宰割,不說了,說多了都是淚!node

項目用的ui框架是antd-design,相信開發過react項目的都對它不陌生,幾乎是react項目首選的ui庫,畢竟阿里出廠,質量保證,可是坑爹的是它的Modal組件沒有拖拽功能,並且官方明確表示,未來也沒有支持的計劃,在github上跟蹤相關issue, 的確找到了幾個大神改造後的版本,我都一一驗證了,可是總有各類各樣的問題,沒辦法,只能本身動手實現了,時間比較緊張,花了大半天時間擼出來的,比較簡單粗暴,主要實現了下面的功能react

1. 鼠標點擊title區域能夠開始拖拽,鼠標擡起中止拖拽
2. 模態框只能在可視區內拖動,不會拖出可視區
3. 對於一個頁面有多個模態框的狀況,先點擊A,再點擊B,可讓B在A的上面,若是項目中的模態框不設置蒙層,那麼能夠實現點擊誰就出如今上面
複製代碼

待完善:git

1. Modal的center屬性必須是true,即垂直居中,不然沒法準確計算出拖拽原點
2. 拖拽不夠流暢
複製代碼

友情使用提示github

1. 對於一個頁面有多個模態框的狀況,,好比 a -> b -> c,那麼必定要給 a的zIndex設置大於1000的數字,後續的b,c數值依次變大,即保證 c > b > a > 1000,不然點擊會出現錯亂
2. 模態框的自定義title的類別忘記添加cousor:move,否則看起來怪怪的,哈哈
複製代碼

anyway, 我只是分享個人思路出來給你們,後續遇到問題你們能夠一塊兒討論解決bash

上面的功能基本知足咱們項目的需求了,下面粘貼個人代碼,重要的地方我都寫註釋了,看不懂的小夥伴歡迎留言給我,有時間我會盡力幫忙解答antd

import React from "react";
import {Modal} from "antd";

// 可控長度的隨機數拼接時間戳成產惟一id
const createUniqID = length=>Number(Math.random().toString().substr(3,length) + Date.now()).toString(36);

class AntdDragModal extends React.PureComponent {
    constructor(props) {
        super(props);
        this.id = createUniqID(10);
        this.dragWrapDom = null;// 包裹拖拽元素的元素
        this.dragDom = null // 拖拽的目標元素,在Modal組件中對應class爲.antd-modal的元素
        this.dragging = false; // 是否拖拽的狀態標誌
        this.tLeft = 0; // 
        this.tTop = 0; //   座標軸
        //modal的左上角相對於屏幕左上角的偏移,也是拖拽原點
        this.points = [0, 0] // 拖拽原點
        this.rect = [0, 0, 0, 0] // 記錄可視區寬高和拖拽元素寬高
        this.onMouseDown = this.onMouseDown.bind(this)
        this.onMouseUp = this.onMouseUp.bind(this)
        this.onMouseMove = this.onMouseMove.bind(this)
        this.onContentMoseDown = this.onContentMoseDown.bind(this)
    }

    componentDidMount() {
        this.getDragDom();
    }
    
    componentDidUpdate() {
        this.getDragDom();
    }

    /*
     * 在定時器中使用原生方式來獲取dom。
     * */
    getDragDom() {
        setTimeout(() => {
            // 獲取惟一標示元素
            const dragWrapDom = document.getElementsByClassName(`d_${this.id}`)[0];
            if (dragWrapDom) {
                this.dragWrapDom = dragWrapDom;
                // 獲取真正的拖動元素
                let dragDom = dragWrapDom.getElementsByClassName('ant-modal')[0]
                if (dragDom) {
                    this.dragDom = dragDom
                    let modalWidth = this.dragDom.offsetWidth;
                    let modalHeight = this.dragDom.offsetHeight;
                    let screenWidth = window.innerWidth
                    let screenHeight = window.innerHeight
                   //原點不是屏幕左上角,由於dragDom是相對定位,一開始水平垂直居中,因此原點是dragDom距離屏幕左上角的偏移
                    this.points = [(screenWidth - modalWidth) / 2, (screenHeight - modalHeight) / 2]
                    this.rect = [screenWidth, screenHeight, modalWidth, modalHeight]
                }
            }
        });
    };

    onMouseDown(e) {
        e.preventDefault();
        this.dragging = true; // 激活拖拽狀態
        /*
        ** 實現點擊後,當前浮層在最上面
        ** 將當前全部涉及可拖拽的浮層的 zIndex = 999
        ** 將當前拖拽目標的 zIndex = 1000
        **/
        const nodeList = document.getElementsByClassName("drag_modal");
        if (nodeList.length > 0) {
            Array.from(nodeList).forEach(item => {
                item.style.zIndex = 999
                if (item.previousElementSibling) {
                    item.previousElementSibling.style.zIndex = 999
                }
            });
            this.dragWrapDom.style.zIndex = 1000;
            /*
            ** 若是mask屬性設置爲true的話,this.dragWrapDom.previousEleme** ntSibling就是蒙層,蒙層的zIndex須要和本身的模態框同步,否**  則一個頁面多個模態框點擊會錯亂
            */
            if (this.dragWrapDom.previousElementSibling) {
                this.dragWrapDom.previousElementSibling.style.zIndex = 1000;
            }
        }

        /*
        * getBoundingClientRect: 返回一個 DomRect 對象
        *   包含該元素的 top、right、bottom、left 值,對應的是到屏幕上方和左邊的距離,單位 px
        * */
        const dragDomRect = this.dragDom.getBoundingClientRect();
        /*
        * e.clientX、e.clientY
        *   獲取鼠標的座標位置
        * */
        // 鼠標按下時和選中元素的座標偏移,是相對於拖拽原點的
        this.tLeft = e.clientX - dragDomRect.left + this.points[0]; 
        this.tTop = e.clientY - dragDomRect.top + this.points[1];  
        this.onMouseMove(this.dragDom);
    };

    onMouseUp(e) {
        e.preventDefault();
        this.dragging = false; // 中止移動狀態
        document.onmousemove = null; // 中止鼠標移動事件
    };

    onMouseMove(node) {
        document.onmousemove = e => {
            e.preventDefault();
            if (this.dragging) {
                let left = e.clientX - this.tLeft
                let top = e.clientY - this.tTop
                // 保證模態框在可視區內
                if (this.points[0] + left > 0 && this.points[0] + left + this.rect[2] < this.rect[0]) {
                    if (this.points[1] + top > 0 && this.points[1] + top + this.rect[3] < this.rect[1]) {
                        node.style.left = left + 'px';
                        node.style.top = top + 'px';
                    }
                }
            }
        };
    };
    // 點擊modal的content部分也中止模態框的拖拽
    onContentMoseDown(e) {
        this.dragging = false; 
        document.onmousemove = null; 
    }


    render() {
        const {
            zIndex = 999, children, wrapClassName
            title,
            width = 520, ...otherProps
        } = this.props
        return (
            <Modal
                zIndex={zIndex}
                width={width}
                wrapClassName={`drag_modal d_${this.id} ${wrapClassName}`}
                title={
                    <div
                        className="drag_title"
                        onMouseDown={this.onMouseDown}
                        onMouseUp={this.onMouseUp}
                    >
                        {title}
                    </div>
                }
                {...otherProps}
                centered={true}
            >
                <div onMouseDown={this.onContentMoseDown}>
                    {children}
                </div>
            </Modal>
        );
    }
}

export default AntdDragModal;

複製代碼
相關文章
相關標籤/搜索