react实现组件拖拽自定义模板(react-dnd),组件自定义大小功能(react-rnd)!

2,075 阅读4分钟

前言:之前需要做一个关于拖拽实现自定义模板的功能一开始也是一脸懵逼的,后面通过查询一些资料和广大群友的帮助完成了该功能!该文章仅介绍功能所需内容不做过多的功能介绍,第一次记录,不喜勿喷!

正文:本文章只做必要的解释,大部分解释在代码中!

一:react-dnd , 官网地址:react-dnd.github.io/react-dnd/d…

1.1 下载

yarn add react-dnd react-dnd-html5-backend
npm install react-dnd react-dnd-html5-backend

1.2 引入 index.jsx

import { DndProvider } from 'react-dnd';
import {HTML5Backend} from 'react-dnd-html5-backend';
const reactDnd = () => {
	return (
		<div className="AppBox">
			<DndProvider backend={HTML5Backend}>
				<Ground />
			</DndProvider>
		</div>
	)
}
export default reactDnd;

DndProvider:组件的应用程序提供React-DnD功能。必须通过backendc参数将其注入后端,但是也可以将其注入window对象。(可以理解为:拖拽发生在DndProvider这个盒子里面)

1.3 拖拽盒子 Container.jsx

const Container = () => {
    const [dataType, setDataType] = useState()
    const [wh, setWh] = useState({
        leftWidth: 0,
        rightWidth: 0
    })
    useEffect(() => {
        setTimeout(() => {
            var div = document.getElementById("GroudBox_left");
            var w =Math.round(div.offsetWidth);    
            var h =Math.round( div.offsetHeight);    
            setWh({
                leftWidth: w, rightWidth: h
            })
        }, 500)
    }, [])
    //拖拽组件数据
    let list = [
        { title: 'Drag me around3', id: 3, left: 0, top: 0,
        style:{width:"100px",height:"100px",background:"darkkhaki"} 
        ]
    const data = {
        setDataType,
        dataType,
        list,
        wh
    }

    return (
        <div className="GroudBox" id="GroudBox">
            <div className="GroudBox_left" id="GroudBox_left">
            //拖拽组件
                {list.map((item, index) => {
                    return (
                        <Btn key={index} item={item} {...data} />
                    )
                })}
            </div>
            <div className="GroudBox_right">
                <DustBin {...data} />//拖拽模板
            </div>
        </div>
    )
}
export default Container;

这个组件很简单,包含了一个左侧需要拖拽的组件右侧的一个拖拽进的模板

1.4 被拖拽的组件 Btn.jsx

useDrag: 声明拖动源,用于将当前组件用作拖动源的钩子(drag 这个组件的是用来拖拽的)

import { Button } from "antd";
import { useDrag, useDrop } from "react-dnd";
import { ItemTypes } from "../ItemTypes";
const Btn = ({
    item,
    setDataType
}) => {
    const [{ isDragging }, drag] = useDrag(() => (
        {
            type: ItemTypes.BOX,//必填,并且必须是字符串,ES6符号。只有注册为相同类型的放置目标才会对此项目做出反应
            end: (monitor) => {
                console.log("拖动的组件", item);
                setDataType(item)
            },
            //监听
            collect: (monitor) => ({
                //监听是否在动
                isDragging: monitor.isDragging(),
                getDropResult: monitor.getDropResult()
            })
        }
    ))
    
  
    return (
            <Button
                ref={drag} type="primary" style={{ marginTop: "1rem" }}>
                组件{item.id}
            </Button>
    )
}
export default Btn;

1.5 ItemTypes.jsx

export const ItemTypes = {
    BOX: 'box',
  }

主要是绑定type的目标类型

1.6 拖拽模板 DustBin.jsx

useDrag: 声明拖动范围,用于将拖动源的钩子拖入的范围(drop 这个组件接受来拖拽来的组件)

import { useState, useEffect, useCallback } from 'react'
import { useDrop } from 'react-dnd'
import { ItemTypes } from '../ItemTypes.js';
import { Rnd } from "react-rnd";

let data, box
const DustBin = ({ dataType, list, wh }) => {
    const [boxes, setBoxes] = useState([
      { top: 20, left: 80, title: 'Drag me around', id: 1 },
        { top: 180, left: 20, title: 'Drag me too', id: 2 },
       ])
 
    //当前组件拖动
    const moveBox = useCallback(
        (id, left, top) => {
            boxes.forEach(item => {
                if (item.id === id) {
                    item.left = left
                    item.top = top
                }
            })
            setBoxes([...boxes])
        },
        [boxes, setBoxes],
    )
   
    const [, drop] = useDrop(
        () => ({
            accept: ItemTypes.BOX,
            drop(item, monitor) {
               console.log(item)//拖动的组件数据
            },
        }),
        [moveBox],
    )
   
    return (
        <div ref={drop} className="template">
            //拖入的组件
          {boxes.map(item)=>{
              return(
              <div>
              {item.title}
              </div>
              )
          }}
        </div>
    )
}
export default DustBin;

drop(item, monitor): 选填。当兼容项目放在目标上时调用。您可以返回undefined或纯对象。如果返回一个对象,它将成为放置结果,并且可用于其拖动源中的endDrag方法monitor.getDropResult()。 主要的作用是获取拖动后获取当前组件最后的X,Y的值(monitor.getDifferenceFromInitialOffset())

1652768569766.gif

后面发现react-dnd只能拖动组件在拖入模板盒子的时候无法获取到当前的数据,所以无法满足从左拖拽到右 放入模板盒子,可能是没有仔细看官网的缘故,有其他办法的没有小伙伴可以留言,下面是我的解决方案

DustBin.jsx完整代码

import { useState, useEffect, useCallback } from 'react'
import update from 'immutability-helper'
import { useDrop } from 'react-dnd'
import { ItemTypes } from '../ItemTypes.js';


let data, box
const DustBin = ({ dataType, list, wh }) => {
    const [boxes, setBoxes] = useState([])
    const [typeId, setTypeId] = useState()
    useEffect(() => {
        console.log("拿到的组件", dataType);
        data = dataType
    }, [dataType])
    useEffect(() => {
        console.log("拿到的宽高", wh);
        box = wh
    }, [wh])
    //当前组件拖动
    const moveBox = useCallback(
        (id, left, top) => {
            boxes.forEach(item => {
                if (item.id === id) {
                    item.left = left
                    item.top = top
                }
            })
            setBoxes([...boxes])
        },
        [boxes, setBoxes],
    )
    //新组件拖入
    const newMoveBox = useCallback(
        (id, left, top) => {
            list.forEach(item => {
                if (item.id === id) {
                    item.left = left
                    item.top = top
                    boxes.push(item)
                }
            })
            setBoxes([...boxes])
        },
        [boxes, setBoxes],
    )
    const [, drop] = useDrop(
        () => ({
            accept: ItemTypes.BOX,
            drop(item, monitor) {
                const delta = monitor.getDifferenceFromInitialOffset()
                const newdelta = monitor.getClientOffset()
                if (item.id) {
                    const left = Math.round(item.left + delta.x)
                    const top = Math.round(item.top + delta.y)
                    moveBox(item.id, left, top)

                } else {
                    const left = Math.round(delta.x - box.leftWidth)
                    const top = Math.round(newdelta.y)
                    setTimeout(() => {
                        console.log(data);
                        newMoveBox(data.id, left, top)
                    }, 500)
                }
                return undefined
            },
        }),
        [moveBox],
    )

    useEffect(() => {
        console.log(boxes);
    }, [boxes])
    const boxDataBtn = (id, x, y) => {
        console.log(id, x, y);
        boxes.forEach(item => {
            if (item.id === id) {
                item.left = 310;
                item.top = 224;
                console.log(item);
            }
        })
        setBoxes([...boxes])
    }
    const functionBtn = {
        boxDataBtn
    }
    return (
        <div ref={drop} className="template">
            {boxes.map((key, index,) => {
                const { id, left, top, style } = key
                return (
                   <div>222</div>
                )
            })}
        </div>
    )
}
export default DustBin;

简单说明一下,在Btn.jsx中的useDrag里面获取到被拖拽的组件(见Btn.jsx -14行)后再放入DustBin.jsx的boxes中这样就能准确的拿到当前拖拽的组件放入模板中了!

1652769037859 (1).gif

二:react-rnd 地址:github.com/bokuweb/rea…

这是一个自定义组件的拖动,大小的一个插件

下载

npm i -S react-rnd

yarn add react-rnd

2.1使用



import { Rnd } from "react-rnd";


const DustBin = ({ dataType, list, wh }) => {
   

    return (
                    <Rnd
                        key={key.id + index}
                        default={{
                            x: left,
                            y: top,
                            width: 200,
                            height: 200
                        }}
                        bounds="parent"//拖动范围
                        /*
                        parent限制节点的 offsetParent 内的移动位置相对或绝对的最近节点window, body, 
                        选择器如.fooClassName
                        */
                        style={
                            {
                                position: 'absolute',
                                cursor: "pointer",
                                left,
                                top,
                                ...style
                            }
                        }
                        onDragStop={(e, d) => {
                            console.log(d.x, d.y);
                            console.log(id);
                          
                        }}
                        lockAspectRatio={false}//盒子的尺寸列如(16/9)
                        className="rndBox"
                        onResizeStop={(e, direction, ref, delta, position) => {
                            // this.setState({
                            //   width: ref.style.width,
                            //   height: ref.style.height,
                            //   ...position,//拉伸后的位置
                            // });

                        }}
                    >
                        2222
                    </Rnd>
      
    )
}
export default DustBin;

1652769750203.gif

总结

这次的功能一共用了两个东西 react-dnd+react-rnd,不是必要的东西就没有写备注 如果没有满足要求的话 请前往官网查看!

再次感谢群友推荐的插件 感谢头上有煎饺鸽鸽!