There was a recent requirement for drag and drop support for the product navigation bar. Although the open source community already has a number of mature drag and drop libraries, for code controllability and customizability, write your own.

The selection

In terms of selection, front-end drag-and-drop functions are implemented in several ways: 1. Through style layout + mouse events, such as @shopify/ Draggable, 2. Canvas drawing, such as Konva, 3. Drag&Drop interface, such as Dragula

After some research, the original Drag&Drop scheme was finally selected for the following reasons: 1. The original Drag&Drop event conforms to the development trend of JS language; 2. Compatibility meets project requirements; 3, Can I use… Is described as follows:

The event

A drag-and-drop action naturally involves two elements: drag elements and drop area elements. There are a total of eight events associated with it, including three events bound to the drag element: drag, dragstart, and dragend. The remaining five events are bound to the release area element: Dragenter, Dragover, Dragleave, DragExit, and DROP. Refer to MDN for specific definitions

Define draggable elements

In the browser, there are three elements that can be dragged by default: 1. Selected text; 2. Pictures; To convert other elements into draggable elements, add draggable=”true”, as in:

<div draggable="true"></div>
Copy the code

Note: This cannot be omitted, as in:

<div draggable></div>
Copy the code

Is invalid.

Define a releasable area

To make a block of elements releasable, first bind the Dragenter or Dragover event and then block the event, as follows:

<div ondragover="return false">
<div ondragover="event.preventDefault()">
Copy the code

Since the default behavior of both events is to “not fire” the DROP, to define a releasable region, do the opposite.

The DataTransfer object

The most important step in a complete drag-and-drop operation, besides dragging an element and releasing it in the specified area, is to display the information carried by the element in the released area. For example, dragging and dropping an image essentially takes the SRC property value of the dragged image and, when it is released, displays an image with the same SRC in the release area. And that information is stored in the DataTransfer object. For non-default drag-and-drop elements, the information they contain needs to be set in the dragstart event, using datatransfer.setData (), as in:

dragItem.ondragstart = e= > {
  e.dataTransfer.setData('text/plain'.'drag info');
}
Copy the code

If you want to drag, the images of the custom, also can call the dataTransfer. SetDragImage, such as:

dragItem1.ondragstart = e= > {
  const img = new Image(); 
  img.src = 'img_url.jpg'; 
  e.dataTransfer.setDragImage(img, 0.0);
}
Copy the code

In the drop event, you can retrieve information about an element and display the specified information in a specific area via DOM manipulation, such as:

dropArea.ondrop = e= > {
  e.preventDefault();
  const data = event.dataTransfer.getData("text/plain");
  const div = document.createElement("div");
  div.textContent = data;
  e.target.appendChild(div);
}
Copy the code

The DataTransfer object also has a pair of properties that ensure that the release area can release only certain types of drag elements, namely dropEffect and Effectalhoward. Effectalhoward can only be set in the Dragstart event, and in the Dragenter or Dragover event, the dropEffect value needs to be set to equal Effectalhoward in order to trigger the drop event. Such as:

dragItem.ondragstart = e= > {
  e.dataTransfer.effectAllowed = "move";
}

dropArea.ondragover = e= > {
  e.preventDefault();
  e.dataTransfer.dropEffect = "move";
}
Copy the code

For details on other attributes and methods, see MDN

Cross-terminal capability

Cross-terminal capability is the biggest feature of DRAG&DROP. The most common cross-terminal requirement is to drag and drop files from the user’s local location to a specific area of the browser for uploading. In the drop event of the specified region, the DataTransfer object files property is used to obtain the file list information, such as:

dropArea.ondrop = e= > {
  e.preventDefault();
  const files = e.dataTransfer.files;
  if (files.length) {
    Array.prototype.forEach.call(files, f => {
      console.log(f.name); // Prints the file name}); }}Copy the code

Practice with React

The React project uses drag&drop, which follows the React data-driven principle of event -> data ->DOM updates. Therefore, the React project can manipulate component object properties instead of passing data through DataTransfer objects, as mentioned earlier, to keep the data flow clear. But in addition, in practical practice, there are still some problems that need special treatment. Details are as follows:

1. Must be reserveddataTransfer.setData

At first, in order to ensure clear data flow, onDragStart was bound to the React component, which was only responsible for monitoring events, data changes, and transferring all component properties. However, it encountered compatibility problems that Firefox could not drag and drop. Add draggable=”true”; 2. Bind event dragStart; Datatransfer. setData sets data in dragstart, so even if e.datatransfer. setData(‘text’, ‘); Set an empty string, which must also be added.

2. Prevent cross-terminal dragging or illegal dragging

Drop&drag cross-terminal capabilities can also be a distraction at times. In projects, if you open two browser tabs on the same page without judgment, drag elements can be dragged across tabs, potentially causing unexpected bugs. To do this, you need to add judgment. A way to generate a random character during component instance construction to tag drag-and-drop elements with datatransfer. setData. Also, a judgment is performed in the DROP event. Of course, if drag and drop elements and drop areas belong to different components, they need to generate random characters in their parent components, which are passed to the two child components in the form of props.

3. Prevent Firefox from automatically opening a new page

In the aforementioned tagging of drag and drop elements, we initially used the following notation:

e.dataTransfer.setData('text', uniqDataTransferTag);
Copy the code

Results In Firefox, each time the drop event is triggered, the browser automatically opens a new TAB and searches for uniqDataTransferTag (random characters). E.preventdefault () is called in the drop event to prevent bubble propagation (), but it still does not work. It may be related to the SyntheticEvent mechanism of React. MIME Type = MIME type = MIME type = MIME type = MIME type = MIME type

e.dataTransfer.setData('ucloud_drag_tag', uniqDataTransferTag);
Copy the code

4. Throttling and avoiding event collection

In your project, you need to determine the current position of the element being dragged and perform DOM operations in onDragOver. By definition, the dragover event is triggered every few hundred milliseconds as the element is dragged onto the free region, and obviously doing nothing can be very bad for performance. Here, the natural idea of throttle optimization. Since throttling is an asynchronous operation, according to React’s SyntheticEvent, the event object is removed after the current event loop ends and cannot be accessed in an asynchronous operation unless e.pineist () is called.

5, HACK drag elements during the drag process, to achieve the “dragged” visual effect

According to the designer’s requirements, the desired element in the project should be dragged after the start of the drag, as shown below:



css class

transform: translateX(-9999px);
Copy the code

2. Add styles to drag and drop elements:

transition: transform 0.1s;
Copy the code

3. After the drag begins, add the CSS class from the first step above.

6. Long press element to activate drag and drop effect

According to the interaction design, it is necessary to implement a certain amount of time before the drag-and-drop can be triggered. At first, the scheme used was to bind the mouse event mousedown, trigger setTimeout, trigger state updates when a fixed length of time is reached, and change the draggable value of the draggable element. However, the actual test found that this method has a certain failure rate, that is, it has reached the long press time, but still can not drag and drop. Moreover, the problem is even more pronounced in Firefox. Presumably, draggable updates occasionally lag behind dragstart events, causing drag and drop to fail. Instead, add a component attribute as a flag, update the flag in a mousedown event, and always set draggable to true. As follows:

// the mousedown event handler
handleLongPress = e= > {
    this.resetDragTimer(); // Clear the timer

    return (this.triggerDragTimer = setTimeout(() = > {
      this.isMenuDraggable = true; // Check the flag
    }, this.triggerDragInterval));
};

// dragstart event handler
handleDragStart = e= > {
    if (!this.isMenuDraggable) {
        e.preventDefault();
    } else{... }};Copy the code

conclusion

As a native Drag&Drop API, Drag&Drop can be implemented with minimal code, which may seem “simple” but isn’t. In practice, you still need to have a good understanding of the official interface definition and the differences between browsers to avoid unknown errors. In a data-driven framework like React, however, how to handle event listeners without disrupting the data flow of components still requires careful design.