preface

React-dnd: React drag-libraries react drag-libraries react drag-libraries react drag-libraries react drag-libraries react drag-libraries react drag-libraries

The following is a GIF with the hooks:

The goal is to implement a useDrag and useDrop hooks that make it easy for elements to be dragged, and that can be customized to pass messages during the drag lifecycle, as follows (a few drag-triggered events are also introduced).

  • Dragstart: When the user starts dragging, the event is triggered on the node being dragged. The target attribute of this event is the node being dragged.
  • Dragenter: Triggered once on the current node when dragged into the current node. The target attribute of this event is the current node. Normally, the listener for this event should specify whether to allow the dropped data to be dropped on the current node. If the current node does not have a listener for the event, or if the listener does nothing, it means that data is not allowed to be dropped at the current node. The visual drag to the current node is also set in the listener function for this event.
  • Dragover: Fires continuously (hundreds of milliseconds apart) on the current node when dragged over it. The target attribute of this event is the current node. The difference between this event and the Dragenter event is that the Dragenter event fires when entering the node, and then the Dragover event continues to fire as long as it does not leave the node.
  • Dragleave: Triggered on the current node when the drag operation leaves the current node scope. The target attribute of this event is the current node. If you want to visually display the drag off operation current node, set it in the listener function for this event.

Use method + source code

class Hello extends React.Component<any, any> { constructor(props: any) { super(props) this.state = {} } render() { return ( <DragAndDrop> <DragElement /> <DropElement /> </DragAndDrop> )  } } ReactDOM.render(<Hello />, window.document.getElementById("root"))Copy the code

As mentioned above, the DragAndDrop component is used to pass messages to all components that use useDrag and useDrop, for example, the element being dragged is the DOM, or you can add other information to it if you want. Let’s see how it works.

Const DragAndDropContext = react.createcontext ({DragAndDropManager: {}}); const DragAndDrop = ({ children }) => ( <DragAndDropContext.Provider value={{ DragAndDropManager: new DragAndDropManager() }}> {children} </DragAndDropContext.Provider> )Copy the code

As you can see, the react Context API is used to pass messages. The main thing is the DragAndDropManager. Let’s look at the implementation

export default class DragAndDropManager { constructor() { this.active = null this.subscriptions = [] this.id = -1 } setActive(activeProps) { this.active = activeProps this.subscriptions.forEach((subscription) => subscription.callback())  } subscribe(callback) { this.id += 1 this.subscriptions.push({ callback, id: this.id, }) return this.id } unsubscribe(id) { this.subscriptions = this.subscriptions.filter((sub) => sub.id ! == id) } }Copy the code

SetActive is used to record the element of the drag. UseDrag: hooks call setActive to pass in the DOM element of the drag.

In addition, I also added the API for subscribing events, SUBSCRIBE, which I’m not currently using, so you can ignore this part in this example until you can add subscription events.

Then let’s look at the use of useDrag, DragElement implementation as follows:

function DragElement() { const input = useRef(null) const hanleDrag = useDrag({ ref: input, collection: {}, // Fill in any message you want to pass to the drop element, Return (<div ref={input}> <h1 role="button" onClick={hanleDrag}> drag element </h1> </div>)}Copy the code

Let’s look at the implementation of useDrag, very simple

export default function useDrag(props) { const { DragAndDropManager } = useContext(DragAndDropContext) const handleDragStart = (e) => { DragAndDropManager.setActive(props.collection) if (e.dataTransfer ! == undefined) { e.dataTransfer.effectAllowed = "move" e.dataTransfer.dropEffect = "move" e.dataTransfer.setData("text/plain", "drag") // firefox fix } if (props.onDragStart) { props.onDragStart(DragAndDropManager.active) } } useEffect(() => { if (! props.ref) return () => {} const { ref: { current }, } = props if (current) { current.setAttribute("draggable", true) current.addEventListener("dragstart", handleDragStart) } return () => { current.removeEventListener("dragstart", handleDragStart) } }, [props.ref.current]) return handleDragStart }Copy the code

What useDrag does is very simple,

  • First, use useContext to retrieve the outermost store data, which is the DragAndDropManager of the code above
  • In useEffect, if a ref is passed in, the dom element’s draggable property is set to true, which is the dragable state
  • We then bind the dragstart event to this element. Note that we remove the event when we destroy the component to prevent memory leaks
  • The handleDragStart event first updates the props. Collection to our props repository, so that each drag element can be passed to the useDrag({collection: {}}) information, through DragAndDropManager setActive (props. Collection), the incoming store to the outside world
  • Then we do something on the dataTransder property to set the element’s drag property to move, and we do it for Firefox compatibility.
  • And finally, whenever we launch the Drag event, the onDragStart event that’s passed in from the outside world will also fire, and we’ll pass in the data from the Store

The useDrop DropElement implementation is as follows:

function DropElement(props: any): any { const input = useRef(null) useDrop({ ref: Input, // e represents the event object of the element being over when the dragOver event occurs. // Collection represents the data stored in the store. // showAfter represents whether the mouse drags the element over the drop element. OnDragOver: (e, collection, showAfter) => {// The top border of the drop element is red if (! showAfter) { input.current.style = "border-bottom: none; Style = "border-top: none; Border-bottom: 1px solid red"}}, // Clear the onDrop style if you release the mouse over the drop element: () => {input.current. Style = ""}, // If it leaves the drop element, the style clears onDragLeave: () = > {input. Current. Style = ""},}) return (< div > < h1 ref = {input} > drop element < / h1 > < / div >)}Copy the code

Finally, let’s look at the implementation of useDrop

Export default function useDrop(props) {// Get props for the outermost store const {DragAndDropManager} = useContext(DragAndDropContext) Const handleDragOver = (e) => {// e is the drag event object e.preventDefault() // getBoundingClientRect overElementHeight = e.currentTarget.getBoundingClientRect().height / 2 const overElementTopOffset = E.c. with our fabrication: urrentTarget. GetBoundingClientRect (). The top / / clientY mouse is the distance to the top of the browser page viewing area const mousePositionY = lientY of e.c. with our fabrication: / / Mousepositiony-overelementtopoffset = mousepositiony-overelementTopoffset >  overElementHeight if (props.onDragOver) { props.onDragOver(e, DragAndDropManager.active, }} // Drop const handledDop = (e: The React. DragEvent) = > {e.p reventDefault () if (props. OnDrop) {props. OnDrop (DragAndDropManager. Active)}} / / dragLeave event const handledragLeave = (e: React.DragEvent) => { e.preventDefault() if (props.onDragLeave) { props.onDragLeave(DragAndDropManager.active) } } // UseEffect (() => {if (! props.ref) return () => {} const { ref: { current }, } = props if (current) { current.addEventListener("dragover", handleDragOver) current.addEventListener("drop", handledDop) current.addEventListener("dragleave", handledragLeave) } return () => { current.removeEventListener("dragover", handleDragOver) current.removeEventListener("drop", handledDop) current.removeEventListener("dragleave", handledragLeave) } }, [props.ref.current]) }Copy the code

GetBoundingClientRect API

rectObject = object.getBoundingClientRect();

Rectobject. top: The distance from the top of the element to the top of the window; Rectobject. right: the distance from the right side of the element to the left side of the window; Rectobject. bottom: Distance from the bottom of the element to the top of the window; Rectobject. left: the distance between the left side of the element and the left side of the window;Copy the code

Some of the code has been written, but the project is too busy to blog. Recently, I was working on RXJS. This was suddenly inspired, and I finished it from 12 o ‘clock to 2 o ‘clock.