reference
- Native HTML5 drag and drop
- HTML Drag and Drop API
- Beautiful react dnd from egghead
Demo
Demo Link
Drag and Drop Life Cycle
Note 1: The onDragExit (dragexit) event is only supported by Firefox and is ignored
Note 2: Only one onDragLeave (dragleave) and onDrop (drop) will occur on the same drop target Component, either onDragLeave or onDrop
Note
Drag source and Drop Target components. Set the draggable attribute of the Drag Source to true. Before being dragged (there is no drop target requirement), data is passed between the two via event.datatransfer (more on the use of dataTransfer later) or global variables.
We typically do the following during the life cycle:
-
Save the drag data source in onDragStart and modify the drag Source style
var dragSrcEl = null function handleDragStart(e) { console.log('drag start:', Id) // this/e.target is current target element this.style.opacity = '0.4' dragSrcEl = this e.dataTransfer.setData('text/html', this.innerHTML) }Copy the code
-
OnDrag events are generally not something we care about, unless your page needs to change depending on the distance and time of the drag
-
Modify the drop Target style in onDragEnter
function handleDragEnter(e) { console.log('drag enter:', e.target.id) this.classList.add('over') } Copy the code
-
In onDragOver, the only thing to do is simply to execute event.preventDefault() to prevent drag by default. It’s not clear what the default action is, but if you don’t do it in the onDragOver event, the onDrop event won’t happen, as it does in Chrome and Firefox. Because this event happens so frequently, it’s not a good place to do something that’s too heavy, like changing styles, which is why we put style changes in onDragEnter, okay
function handleDragOver(e) { console.log('drag over:', e.target.id) e.preventDefault() // Necessary. Allows us to drop. } Copy the code
-
In onDragLeave, restore the drop Target style (if onDrop occurs, onDragLeave does not occur, so restoring the drop Target style is also handled in onDrop or onDragEnd)
function handleDragLeave(e) { console.log('drag leave:', e.target.id) this.classList.remove('over') } Copy the code
-
In onDrop, the real logic for handling drags is generally to implement data exchange
function handleDrop(e) { console.log('drop:', e.target.id) e.preventDefault() // stop the browser from redirection // swap if (dragSrcEl ! = this) { dragSrcEl.innerHTML = this.innerHTML this.innerHTML = e.dataTransfer.getData('text/html') } }Copy the code
Also, on onDrop, event.preventDefault() is almost mandatory to prevent drag by default. However, this is only done for Firefox compatibility, and it is fine not to do this on Chrome. On Firefox, if the data in event.datatransfer is a link, the default action for drag and drop is to jump to that link.
Suppose the dataTransfer set in onDragStart looks like this:
function handleDragStart(e) { e.dataTransfer.setData('text/plain', 'https://www.google.com') // or // e.dataTransfer.setData('text', 'anything') } Copy the code
So on onDrop, if there’s no event.preventdefault (), it looks something like this on Firefox:
The default action for e.datatransfer.setdata (‘text’, ‘anything’) is to jump to https://www.anything.com
But if the setting is e.datatransfer.setdata (‘aaa’, ‘anything’), the skip won’t happen. Anyway, simply add event.preventDefault(). Now, you might say, why do you put something like ‘AAA’ in a dataTransfer, is it okay not to put data in a dataTransfer, and the answer is no, on Firefox if you don’t have data in a dataTransfer, Firefox thinks you don’t really want to drag, and drag fails, so we just have to stuff it with data.
- Why doesn’t HTML5 drag and drop work in Firefox?
The jump does not happen on Chrome, so it is not forced to save data to the dataTransfer in onDragStart as well.
-
Do some cleanup in onDragEnd, such as restoring the drag Source and drop target styles, because onDragLeave and onDrop are not guaranteed to happen, but onDragEnd will definitely happen
function handleDragEnd(e) { console.log('drag end:', e.target.id) // reset dragSrcEl = null this.style.opacity = '' [].forEach.call(cols, function (col) { col.classList.remove('over') }); } Copy the code
Examples and special cases
Drag the Column-1 element through column-2, column-3, and finally down at Column-3. The whole process looks like this:
column-1 (drag source) | column-2 (drop target) | column-3 (drop target) |
---|---|---|
onDragStart | ||
onDrag… | ||
. | onDragEnter | |
. | onDragOver… | |
. | onDragOver | |
. | onDragLeave | |
. | onDragEnter | |
. | onDragOver… | |
onDrag | onDragOver | |
onDrop | ||
onDragEnd |
When running on Chrome, the process works as expected, but when running on Firefox, it is common for the latter Component’s Enter event to appear before the former Component’s Leave event.
React
Handling Drag and Drop in React is similar to handling native events.
renderItem = (item_id, index) => {
const item = this.state.data.items[item_id]
return (
<div className={this.getItemClassName(item)}
key={item.id}
draggable={true}
onDragStart={(e)=>this.handleDragStart(e, item, index)}
onDrag={(e)=>this.handleDrag(e, item)}
onDragEnd={(e)=>this.handleDragEnd(e, item)}
onDragEnter={(e)=>this.handleDragEnter(e, item)}
onDragOver={(e)=>this.handleDragOver(e, item)}
onDragExit={(e)=>this.handleDragExit(e, item)}
onDragLeave={(e)=>this.handleDragLeave(e, item)}
onDrop={(e)=>this.handleDrop(e, item, index)}>
<h1>{item.title}</h1>
</div>
)
}
Copy the code
On Firefox, however, the same problem exists with the native Drag and Drop event, where the latter Component’s Enter event precedes the previous Component’s leave.
To make matters worse, there is a problem in Chrome and Firefox where onDragEnter occurs twice in a row, and onDragLeave occurs twice in a row, as shown below. 69.0.3497.100, Firefox: 62.0)
The problem is that onDragLeave becomes unreliable, and we normally restore the drop Target style in onDragLeave, but actually after the first onDragLeave after two consecutive onDragLeaves, The drop does not leave the drop target, which also responds to the onDragOver event.
I’m not sure if this is a React bug, but I’m not going to handle any logic in onDragLeave right now, and I’m going to put it in onDrop or onDragEnd.
dataTransfer
The dataTransfer is used to transfer data between a drag source and a drop target. You may be wondering why this is needed. Can’t I just put the source data in a global variable?
It actually works if you’re just doing it on the same page, like when you implement React, you have to put it in state.
But HTML5’s Drag and Drop API supports Drag and Drop from a web page to the desktop, or from the desktop to a web page, when global variables are not available, which I think is why dataTransfer is needed. Firefox also forces you to set data to the dataTransfer in onDragStart, even if you’re dragging within the same page, otherwise you don’t think you really want to drag, and drag won’t work. So normally we would assign random values to the dataTransfer. Chrome doesn’t require this.
Since drag and drop from the desktop to a web page is supported, it is possible for dragSrcEl to be null in the onDrop event of the first example above, and we need to add judgments to make the application more robust.
function handleDrop(e) { console.log('drop:', e.target.id) e.preventDefault() // stop the browser from redirection in firefox // swap // drop event maybe caused by draging from desktop, so dragSrcEl maybe null if (dragSrcEl && dragSrcEl ! = this) { dragSrcEl.innerHTML = this.innerHTML this.innerHTML = e.dataTransfer.getData('text/html') } }Copy the code
Let’s print event. DataTransfer in onDrop and drag a file in to see what the value is.
function handleDrop(e) {
console.log(e.dataTransfer)
...
}
Copy the code
Output on Firefox:
Output on Chrome:
In this case, there is no onDragStart event/onDrag, onDragEnd, onDragEnter only/onDragOver/onDragLeave or onDrop event.
However, dataTransfer values on Firefox and Chrome are different, so compatibility handling is required.
Common methods and properties of dataTransfer:
- setData(key, value)
- getData(key)
- setDragImage(imgElement, x, y)
- Effectalhoward: None, copy, copyLink, copyMove, link, linkMove, move, all, and uninitialized
- dropEffect: none, copy, link, move
SetData (key, value) and getData(key) are used together. In HTML5, key can be any value, but if key is something specified in the standard like ‘text’ or ‘text/plain’, ‘URL’ etc., the onDrop without preventDefault() will trigger the default, For example, jump to the url specified in value. (In the past and on some browsers, key only supported fixed values, such as ‘text’ or ‘URL ‘)
SetDragImage () is used to change the image that moves with the cursor during drag. The default is a mirror image of the drag source but with lower transparency. (To be added when needed)
Effectalhoward and dropEffect are also used together. The two values must match in order for the drop to succeed. Otherwise, the drop fails.
Effectalhoward is the value assigned to the drag source in onDragStart, and dropEffect is the value assigned to the placed target in onDragOver. Effectalhoward indicates the type of target that is expected to be placed by the drop source. For example, if the value of Effectalhoward is copy, the place must be placed only if dropEffect is also copy. If Effectalhoward is copyLink, then the acceptable dropEffect must be copy or link.
We set Effectalhoward to copyLink in onDragStart
function handleDragStart(e) {
...
e.dataTransfer.effectAllowed = 'copyLink'
}
Copy the code
In onDragOver, check if id is column-4, then dropEffect is move, and others are copy or link.
function handleDragOver(e) { ... if (e.target.id === 'column-4') { e.dataTransfer.dropEffect = 'move'; } else if (e.target.id === 'column-3') { e.dataTransfer.dropEffect = 'link'; } else { e.dataTransfer.dropEffect = 'copy'; }}Copy the code
So, column-1 to column-3 can be dragged from each other, but not to column-4, as shown below.
In addition, it can be found that different dropEffect, drag and drop mouse style is also different, copy mouse style is a green cross, and link is a link style.
(It’s not clear what practical use effectalhoward and dropEffect have beyond changing the mouse style, and we probably wouldn’t use them as a constraint on whether to drag and drop.)