Mastering HTML5 Drag-and-Drop: Native Implementation, Events, and React Alternatives
This article explains how to implement native HTML5 drag‑and‑drop, details the required draggable attribute, describes all related events, shows a simple JavaScript demo, discusses common pitfalls such as repeated ondragover and ghosting, and compares popular React libraries for drag‑and‑drop.
Final Result
Native Implementation Principle
About Dragging
Image tags are draggable by default; other elements (e.g., div) need the attribute draggable="true" to become draggable.
Drag‑and‑Drop Events
On the drag source
ondragstart – triggered when the user starts dragging the element.
ondrag – triggered while the element is being dragged.
ondragend – triggered after the drag operation completes.
On the drop target
ondragenter – fires when the dragged object enters a container.
ondragover – fires continuously while the dragged object is over another container.
ondragleave – fires when the dragged object leaves a container.
ondrop – fires when the mouse button is released during a drag.
Note: ondragend and ondragover have default behaviors that must be prevented; otherwise the element may leave a ghost image.
element.ondragover = event => {
event.preventDefault();
// ...
};A Simple Native JS Drag Demo
Thinking Process
ondragstart
Obtain the source element.
ondrag
Change the source element's style during dragging.
ondragend
Remove extra styles, retrieve the target index from ondragover, and update the list array.
ondragover
Generate a short‑lived snapshot of the drag path to determine the final target and add movement animations to intermediate elements.
Problem Solving
How to Transfer Data
Method 1: set className="index${index}" and parse the index from event.target.classList – not elegant.
Method 2: set a data-index attribute and read it via event.target.dataset.index – chosen for its semantic clarity.
Nested DOM Causing Repeated ondragover
When the source element has multiple DOM layers, each layer triggers ondragover, leading to data confusion.
Solution: use e.currentTarget instead of e.target to identify the container.
Drag End Ghosting
After dropping, the element may drift back to its original position before moving to the target.
Solution: prevent the default behavior on the parent element's ondragover.
onDragOver={(e) => {
e.preventDefault();
}};Suitable Drag Transition Animation
Use CSS animations to create an upward or downward “making space” effect.
.drag-up {
animation: dragup ease 0.1s 1;
animation-fill-mode: forwards;
}
.drag-down {
animation: dragdown ease 0.1s 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; }
}Other Third‑Party Libraries
react-dnd – larger bundle, TypeScript support, good maintenance, uses HTML5 drag API, repository: https://github.com/react-dnd/react-dnd
react-beautiful-dnd – >100 KB, no TypeScript support, good maintenance, also based on HTML5 drag API, repository: https://github.com/atlassian/react-beautiful-dnd
react-sortable-hoc – smaller bundle, no TypeScript support, unmaintained for over a year, uses HTML5 mouse API, repository: https://github.com/clauderic/react-sortable-hoc/issues
For simple requirements, native implementation remains valuable because the two larger libraries are not lightweight.
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", "drag-down");
over.classList.remove("drag-up", "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);
}
}; {items.map((item, index) => (
return (
<Item
draggable={draggable}
data-index={index}
onDragStart={e => { dragStart(e); }}
onDrag={e => { e.preventDefault(); e.target.style.opacity = '0'; }}
onDragOver={dragOver}
onDragEnd={e => { dragEnd(e); }}
>
// your list data
</Item>
)
)})References
ondragstart – https://www.runoob.com/jsref/event-ondragstart.html
ondrag – https://www.runoob.com/jsref/event-ondrag.html
ondragend – https://www.runoob.com/jsref/event-ondragend.html
ondragover – https://www.runoob.com/jsref/event-ondragover.html
ondragleave – https://www.runoob.com/jsref/event-ondragleave.html
ondrop – https://www.runoob.com/jsref/event-ondrop.html
HTML5 Drag and Drop API – https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
