The final result
Native implementation principles
About the drag and drop
The image of the tag can be dragged by default.
However, other tags (divs, etc.) cannot be dragged. Mouse click and move will not have drag effect. Add attribute draggable=”true” to allow elements to be dragged.
The drag and drop process triggers an event
-
Trigger an event on a drag target (source element) :
- Ondragstart – Triggered when the user starts dragging elements
- Ondrag – Triggered while the element is dragging
- Ondragend – Triggered when the user finishes dragging the element
-
Events that trigger when a target is released:
- Ondragenter – This event is triggered when a mouse-dragged object enters its container scope
- Ondragover – This event is triggered when a dragged object is dragged within the scope of another object container
- Ondragleave – This event is triggered when an object dragged by the mouse leaves its container scope
- Ondrop – This event is triggered when the mouse key is released during a drag
Ondragend and ondragOver Reset the current drag operation to “None “. That is, its default line is that when onDragover is triggered, the movement will fail (creating a residual), so to make an element placement, you need to prevent the default behavior.
element.ondragover = event= > {
event.preventDefault(); // ...
}
Copy the code
A simple JS native drag demo
Thinking about thinking
ondragstart
Get the source element
ondrag
Source element style changes during drag and drop
ondragend
Remove excess styles, get the last target element index in onDragover, and change the list array
ondragover
In a short period of time to generate a snapshot, record the source element drag track through which elements, used to generate the final target element, to the passing element to add displacement animation style
Problem solving
How is data transferred
Idea 1: for each item the className = ${index}, the index by Number (event. The target. The classList. Slice (” 5 “) to the values,
Can achieve the desired effect, but the implementation is not elegant, so abandon this idea
Idea 2: the attribute data – account for each item index = {index}, through the event. The target. The dataset. The index value, conforms to the semantic code, the method is adopted
Nested DOM causes onDragover to fire repeatedly
The onDragover function fires repeatedly when the source element moves within the target element without stopping the drag. If the source element is not a single-layer DOM structure, each DOM layer fires ondragover, causing data chaos
The first red box in the console below is printed when item1 is dragged, and the second red box is printed when Item2 is dragged. You can see that the two-layer DOM structure of Item1 triggers two prints:
<! DOCTYPEhtml>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
.wrapper {
display: flex;
border: 1px solid orangered;
padding: 10px;
}
.col {
border: 1px solid # 808080;
height: 500px;
width: 200px;
margin: 0 10px;
padding: 10px;
}
.item {
border: 1px solid # 808080;
margin: 5px 0;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="col1 col">
<div class="item" id="item1" draggable="true">
<div>item1</div>
</div>
<div class="item" id="item2" draggable="true">item2</div>
</div>
</div>
<script>
let cols = document.getElementsByClassName("col");
for (let col of cols) {
col.ondragover = (e) = > {
e.preventDefault();
console.log(e.target);
};
}
</script>
</body>
</html>
Copy the code
The solution to this problem is to stop using E.turrenttarget and use E.currenttarget instead
Concepts for target and currentTarget:
1. Target occurs in the target phase of the event flow, while currentTarget occurs in the entire phase of the event flow (capture, target, and bubble phases)
This is true only when the target flow is in the target phase.
3. While the event stream is in the capture and bubble phases, target points to the clicked object and currentTarget points to the current event active object, usually the ancestor of the event.
Double shadow appears after drag ends
The so-called double shadow is when we drag to the specified position, the page element we drag first slowly drift to the initial position, and then update to the position we specify
The solution is to add methods on top of the parent element
onDragOver``={(e:) => {
e.``preventDefault``();
}} >
Disable default drag behavior (drift slowly to the initial position is the default drag behavior is disabled)
Appropriate drag and drop transition animation
Make the picture appear to move up or down “give way” effect
.drag-up {
animation: dragup ease 0.1 s 1;
animation-fill-mode: forwards;
}
.drag-down {
animation: dragdown ease 0.1 s 1;
animation-fill-mode: forwards;
}
@keyframes dragup {
from {
margin-top: 10px;
}
to {
margin-top: 50px; }}@keyframes dragdown {
from {
margin-bottom: 10px;
margin-top: 50px;
}
to {
margin-bottom: 50px;
margin-top: 10px; }}Copy the code
Other Third-party libraries
The name of the | react-dnd | react-beautiful-dnd | react-sortable-hoc |
---|---|---|---|
Package volume | larger | Large (> 100 k) | smaller |
TS support | There are | There is no | There is no |
Maintenance is | good | good | Not updated for more than a year |
By reference | event.target.dataset | event.target.dataset | ref |
Use the difficulty | Higher learning costs | The cost of learning is low | The cost of learning is low |
The core to realize | html5 drag API | html5 drag API | html5 mouse API |
The warehouse address | Github.com/react-dnd/r… | Github.com/atlassian/r… | Github.com/clauderic/r… |
In fact, except for the last library, the other two well-known libraries are not very small, so the simple requirement of native implementation is still necessary
react-dnd
Codesandbox. IO/s/a lot/re… (Experience demo)
React DnD builds on the HTML5 Drag and Drop API. Because it can take a screenshot of a dragged DOM node and use it as a “drag preview” so you don’t have to do anything drawing while the cursor is moving, the API is also the only way to handle file deletion events.
However, the HTML5 drag-and-drop API has some drawbacks. It doesn’t work on touch screens, and it offers less customization on IE than other browsers.
This is why the React DnD implementation of the HTML5 Drag and Drop API is pluggable. You don’t have to use it, you can wrap your implementation around native touch events, mouse events, etc. This pluggable implementation is called Backends in React DnD. The library is currently only used in HTML Backend, but may be used in more places in the future.
Backends functions like the React integrated event system: they both abstract away browser differences and handle local DOM events. Unlike React, Backends does not rely on React or React’s integrated event system. In the underlying implementation, what Backends does is convert native Dom events into internal Redux actions that React DnD can handle.
react-beautiful-dnd
React-beautiful-dnd.net lify. App/path = / stor… (Experience demo)
react-sortable-hoc
Clauderic. Making. IO/react – sorta… (Experience demo)
The core code
const [dragged, setDragged] = useState<any> ();const [over, setOver] = useState<any> ();const [draggable, setDraggable] = useState(false);
const dragStart = (e: any) = > {
e.currentTarget.style.backgroundColor = "#fafafa";
setDragged(e.currentTarget);
};
const dragEnd = (e: any) = > {
e.preventDefault();
e.target.style.display = "flex";
e.target.classList.remove("drag-up");
over.classList.remove("drag-up");
e.target.classList.remove("drag-down");
over.classList.remove("drag-down");
const from = cloneDeep(value[dragged.dataset.index]);
const to = cloneDeep(value[over.dataset.index]);
splice(dragged.dataset.index, 1, to);
splice(over.dataset.index, 1.from);
e.target.style.opacity = "1";
e.target.style.backgroundColor = "";
};
const dragOver = (e: any) = > {
e.preventDefault();
const dgIndex = dragged.dataset.index;
const taIndex = e.currentTarget.dataset.index;
const animateName = dgIndex > taIndex ? "drag-up" : "drag-down";
if(over && e.currentTarget.dataset.index ! == over.dataset.index) { over.classList.remove("drag-up"."drag-down");
}
if (!e.currentTarget.classList.contains(animateName)) {
e.currentTarget.classList.add(animateName);
setOver(e.currentTarget);
}
};
Copy the code
{items.map((item, index) = > {
return (<Item
draggable={draggable}
data-index={index}
onDragStart={e= >{ dragStart(e); }} onDrag={(e: any) => { e.preventDefault(); e.target.style.opacity = '0'; }} onDragOver={dragOver} onDragEnd={e => { dragEnd(e); }}> // Your list array data<Item/>)}Copy the code