react實現拖拽模態框

前言

實際開發中,模態框展現數據會經常出現.但不幸的是有時功能開發完瞭,UI同學突然提出需求希望模態框能拖拽.本文使用的模態框由 ant design 3.0 的 Modal 組件封裝而成,如何在不修改原來代碼的基礎上實現拖拽呢.最終效果圖如下:

實踐

1.創建高階組件DragHoc

新建文件ModalDrag/index.js,將下面代碼copy進去

DragObj是具體拖拽的原生js代碼,後面再看

  • DragHoc是創建高階組件的函數,其中參數InnerComponent是需要被改造的模態框組件,函數最終的返回值是增強後的組件
  • render方法中直接返回瞭 <InnerComponent/> ,並沒有返回一個新組件.整個高階組件的作用隻是在輸入組件上加瞭一個ref屬性.有瞭ref,init方法中可以通過 ReactDOM.findDOMNode 獲取到傳入的任意組件的原生dom.拿到dom以後就可以做底層的dom操作或事件綁定以實現拖拽
  • init方法裡加瞭一個延時0s的定時器,由於筆者的項目中InnerComponent是用ant design裡面的Modal封裝而成.在調試的過程中發現,ReactDOM.findDOMNode 隻能返回已經掛載到頁面上的dom元素,否則返回null.而ant design裡面的Modal渲染內容是異步的,因此要使用定時器等到下一幀才能使用findDOMNode得到組件的dom元素.如果InnerComponent裡面不包含異步渲染的代碼,下面的定時器可以刪除
  • 組件卸載時調用destory方法將所有綁定的事件釋放掉

拖拽一個元素通常需要傳入兩個參數.一個是推拽後能移動的區域,對應著上圖中的整個導出表格控件,控件的類名為main_class.另外一個是監聽拖拽的區域,對應著上圖中的頭部,隻有當鼠標在頭部按下時再移動才能拖動表格.頭部的類名為title_class.兩個參數都從外部傳入.如果兩個參數都不傳,默認直接監聽child_node並拖拽child_node

import React from 'react';
import ReactDOM from 'react-dom';
import DragObj from './drag';

//main_class和title_class都是類名
export const DragHoc = (InnerComponent,main_class,title_class) =>
  class extends React.Component {
    componentDidMount() {
      this.init();
    }

    init = () => {
      setTimeout(() => {
        const child_node = ReactDOM.findDOMNode(this.refs.child); //獲取到原生的dom元素
        if (child_node) {
          this.drag_obj = new DragObj(
            main_class?child_node.querySelector(`.${main_class}`):child_node, //隻拖拽類名為 ${main_class} 的div
            title_class?child_node.querySelector(`.${title_class}`):child_node //當鼠標按在類名為 ${title_class} 的div上時才允許拖拽
          );
        }
      }, 0);
    };

    componentWillUnmount() {
      if (this.drag_obj) {
        this.drag_obj.destory();
      }
    }

    render() {
      return <InnerComponent {...this.props} ref="child" />;
    }
  };

如果在實踐中發現拖拽無效,請務必將上面代碼中的child_node打印出來,觀察是否獲取到瞭真實的dom以及它內部是否渲染完整.如果沒有渲染完全,說明InnerComponent包含異步渲染的代碼,要等到渲染完畢後再進行拖拽事件綁定

2.創建拖拽類DragObj

新建文件ModalDrag/drag.js,將下面代碼copy進去

下面是實現拖拽的原生代碼.主要負責對dom元素進行事件綁定以及改變位置等

export default class DragObj {
  start_x0 = 0;
  start_y0 = 0;
  start_x1 = 0;
  start_y1 = 0;
  state = false; //記錄鼠標按鍵是否松開
  delta_x = 0; //相對於原始位置的橫向偏移量
  delta_y = 0; //相對於原始位置的縱向偏移量

  constructor(target, move_item) {
    this.target = target; //被移動的dom元素
    this.move_item = move_item; //接受觸發移動行為的dom元素,一般為模態框的頭部
    this.init();
  }

  init() {
    this.move_item.style.cursor = 'move';
    this.bindEvent();
  }

  destory() {
    this.move_item.removeEventListener('mousedown', this.moveStartFun);
    document.removeEventListener('mousemove', this.movingFun);
    document.removeEventListener('mouseup', this.moveEndFun);
  }

  bindEvent() {
    this.moveStartFun = this.moveStart.bind(this);
    this.movingFun = this.moving.bind(this);
    this.moveEndFun = this.moveEnd.bind(this);
    this.move_item.addEventListener('mousedown', this.moveStartFun);
    document.addEventListener('mousemove', this.movingFun);
    document.addEventListener('mouseup', this.moveEndFun);
  }

  moveStart(e) {
    e.stopPropagation();
    this.state = true; //檢測鼠標是否處於按下的狀態
    this.start_x0 = e.pageX;
    this.start_y0 = e.pageY;
  }

  moving(e) {
    //鼠標移動時的默認操作
    e.stopPropagation();
    e.preventDefault();
    if (!this.state) {
      return false;
    }

    this.start_x1 = e.pageX;
    this.start_y1 = e.pageY;
    this.render();
  }

  moveEnd(e) {
    if (!this.state) {
      return false;
    }
    this.state = false;
    this.delta_x = this.start_x1 - this.start_x0 + this.delta_x;
    this.delta_y = this.start_y1 - this.start_y0 + this.delta_y;
  }

  render() {
    this.target.style.transform = `translate(${
      this.start_x1 - this.start_x0 + this.delta_x
    }px,${this.start_y1 - this.start_y0 + this.delta_y}px)`;
  }
}

3.外部調用

引入高階函數DragHoc,引入需要增強的模態框組件ToastExport

由於筆者在項目中使用 ant design 3.0 中的 Modal 組件做模態框,讓其拖拽隻需要傳遞類名 “ant-modal-content” 和 “ant-modal-header”.

其他場景需要根據靜態模態框組件的dom結構分析哪一部分是要移動的,哪一部分是監聽拖拽的,將這兩部分的類名作為參數傳入

import { DragHoc } from "./index.js";
import ToastExport from "../components/ToastExport/index.js";
//引入靜態的模態框組件(用戶自已定義的模態框組件)
const ToastExportv2 = DragHoc(ToastExport,"ant-modal-content","ant-modal-header"); 
//生成瞭具備拖拽功能的模態框組件

調用DragHoc函數生成ToastExportv2後,接下來就可以頁面上直接使用.如果業務上需要傳遞參數直接加在屬性上

const { visible } = this.props;  //visible控制顯示隱藏模態框
{visible?<ToastExportv2 visible={visible}/>:null}

調用時需要註意,當visible為true時再渲染ToastExportv2,為瞭防止綁定事件時dom還沒開始渲染.visible為false時,組件銷毀會自動調用destory方法解綁已註冊的事件

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: