react實(shí)現(xiàn)拖拽模態(tài)框
前言
實(shí)際開發(fā)中,模態(tài)框展現(xiàn)數(shù)據(jù)會經(jīng)常出現(xiàn).但不幸的是有時功能開發(fā)完了,UI同學(xué)突然提出需求希望模態(tài)框能拖拽.本文使用的模態(tài)框由 ant design 3.0 的 Modal 組件封裝而成,如何在不修改原來代碼的基礎(chǔ)上實(shí)現(xiàn)拖拽呢.最終效果圖如下:
實(shí)踐
1.創(chuàng)建高階組件DragHoc
新建文件ModalDrag/index.js,將下面代碼copy進(jìn)去
DragObj是具體拖拽的原生js代碼,后面再看
- DragHoc是創(chuàng)建高階組件的函數(shù),其中參數(shù)InnerComponent是需要被改造的模態(tài)框組件,函數(shù)最終的返回值是增強(qiáng)后的組件
- render方法中直接返回了 <InnerComponent/> ,并沒有返回一個新組件.整個高階組件的作用只是在輸入組件上加了一個ref屬性.有了ref,init方法中可以通過 ReactDOM.findDOMNode 獲取到傳入的任意組件的原生dom.拿到dom以后就可以做底層的dom操作或事件綁定以實(shí)現(xiàn)拖拽
- init方法里加了一個延時0s的定時器,由于筆者的項(xiàng)目中InnerComponent是用ant design里面的Modal封裝而成.在調(diào)試的過程中發(fā)現(xiàn),ReactDOM.findDOMNode 只能返回已經(jīng)掛載到頁面上的dom元素,否則返回null.而ant design里面的Modal渲染內(nèi)容是異步的,因此要使用定時器等到下一幀才能使用findDOMNode得到組件的dom元素.如果InnerComponent里面不包含異步渲染的代碼,下面的定時器可以刪除
- 組件卸載時調(diào)用destory方法將所有綁定的事件釋放掉
拖拽一個元素通常需要傳入兩個參數(shù).一個是推拽后能移動的區(qū)域,對應(yīng)著上圖中的整個導(dǎo)出表格控件,控件的類名為main_class.另外一個是監(jiān)聽拖拽的區(qū)域,對應(yīng)著上圖中的頭部,只有當(dāng)鼠標(biāo)在頭部按下時再移動才能拖動表格.頭部的類名為title_class.兩個參數(shù)都從外部傳入.如果兩個參數(shù)都不傳,默認(rèn)直接監(jiān)聽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 //當(dāng)鼠標(biāo)按在類名為 ${title_class} 的div上時才允許拖拽 ? ? ? ? ? ); ? ? ? ? } ? ? ? }, 0); ? ? }; ? ? componentWillUnmount() { ? ? ? if (this.drag_obj) { ? ? ? ? this.drag_obj.destory(); ? ? ? } ? ? } ? ? render() { ? ? ? return <InnerComponent {...this.props} ref="child" />; ? ? } ? };
如果在實(shí)踐中發(fā)現(xiàn)拖拽無效,請務(wù)必將上面代碼中的child_node打印出來,觀察是否獲取到了真實(shí)的dom以及它內(nèi)部是否渲染完整.如果沒有渲染完全,說明InnerComponent包含異步渲染的代碼,要等到渲染完畢后再進(jìn)行拖拽事件綁定
2.創(chuàng)建拖拽類DragObj
新建文件ModalDrag/drag.js,將下面代碼copy進(jìn)去
下面是實(shí)現(xiàn)拖拽的原生代碼.主要負(fù)責(zé)對dom元素進(jìn)行事件綁定以及改變位置等
export default class DragObj { ? start_x0 = 0; ? start_y0 = 0; ? start_x1 = 0; ? start_y1 = 0; ? state = false; //記錄鼠標(biāo)按鍵是否松開 ? delta_x = 0; //相對于原始位置的橫向偏移量 ? delta_y = 0; //相對于原始位置的縱向偏移量 ? constructor(target, move_item) { ? ? this.target = target; //被移動的dom元素 ? ? this.move_item = move_item; //接受觸發(fā)移動行為的dom元素,一般為模態(tài)框的頭部 ? ? 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; //檢測鼠標(biāo)是否處于按下的狀態(tài) ? ? this.start_x0 = e.pageX; ? ? this.start_y0 = e.pageY; ? } ? moving(e) { ? ? //鼠標(biāo)移動時的默認(rèn)操作 ? ? 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.外部調(diào)用
引入高階函數(shù)DragHoc,引入需要增強(qiáng)的模態(tài)框組件ToastExport
由于筆者在項(xiàng)目中使用 ant design 3.0 中的 Modal 組件做模態(tài)框,讓其拖拽只需要傳遞類名 “ant-modal-content” 和 “ant-modal-header”.
其他場景需要根據(jù)靜態(tài)模態(tài)框組件的dom結(jié)構(gòu)分析哪一部分是要移動的,哪一部分是監(jiān)聽拖拽的,將這兩部分的類名作為參數(shù)傳入
import { DragHoc } from "./index.js"; import ToastExport from "../components/ToastExport/index.js"; //引入靜態(tài)的模態(tài)框組件(用戶自已定義的模態(tài)框組件) const ToastExportv2 = DragHoc(ToastExport,"ant-modal-content","ant-modal-header"); //生成了具備拖拽功能的模態(tài)框組件
調(diào)用DragHoc函數(shù)生成ToastExportv2后,接下來就可以頁面上直接使用.如果業(yè)務(wù)上需要傳遞參數(shù)直接加在屬性上
const { visible } = this.props; ?//visible控制顯示隱藏模態(tài)框 {visible?<ToastExportv2 visible={visible}/>:null}
調(diào)用時需要注意,當(dāng)visible為true時再渲染ToastExportv2,為了防止綁定事件時dom還沒開始渲染.visible為false時,組件銷毀會自動調(diào)用destory方法解綁已注冊的事件
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。