Preface
Native drag events have many criticisms.
For example, the element that follows the mouse pointer when dragging is too simple.
For example, dataTransfer is inaccessible except for start and drop, which makes drop elements less likely to accept drop.
For example, there is no elegant way to set the mouse pointer during dragging.
For example, touch dragging is not supported.
With compatibility with multiple input devices and fancy features, I need to simulate a set of drag and drop events to fulfill my requirements.
This issue is mainly about a drag-and-drop element scheme that I explored to simulate the native browser, compatible with a variety of input devices (PC/mobile adapters)
PointerEvent
Today's devices that can run the web are not only computers, but also mobile phones/tablets, which can be entered with a mouse/touch/digital pad or something...
PointerEvent It's for this.
Its purpose is to provide a unified event model for multiple input devices. It is already part of the W3C standard.
All browsers are compatible at this stage. Stamp detailed documents about it MDN Pointer Event
Let's first look at what events it provides
- onpointerdown //Element has a point pressed, possibly a mouse/touch/pressure pen
- onpointermove //A point on an element is moved
- onpointerup //Element raised at some point
- onpointerenter //A point is within the range of elements (including child elements beyond size) and is not obscured.
- onpointerleave //A point is out of the element range (including child elements beyond size) and is not obscured.
- onpointerover //Element itself or a point in a child element enters an event, distinguishable by e.target
- onpointerout //Element itself or a point of a child element that leaves an event, distinguishable by e.target
- onpointercancel //Trigger when an element is pressed and the browser thinks that there will be no more follow-up events (some browsers do not yet support it)
- I've provided you with an event test page that contains almost all the possible scenarios.
This can help you better understand the trigger mechanism of an event. You can test it by stamping the link below:
Codepen - PointerEvent Trigger Mechanism Test
start
With these events in mind, you'll probably consider using down move up to implement logic that simulates drop s.
let div = document.querySelector('#a'); div.addEventListener('pointerdown', (e) => { document.addEventListener('pointerup', pointerup); document.addEventListener('pointermove', pointermove); document.addEventListener('pointercancel', pointerup); function pointerup(e) { document.removeEventListener('pointerup', pointerup); document.removeEventListener('pointermove', pointerup); document.removeEventListener('pointercancel', pointerup); } function pointermove(e) { } });
That's good. A simple drag and drop event is done.
At this point, you might think about registering the enter and leave events for each object that needs to be dropped, creating a global variable to mark the currently active drop object, and determining that variable when up.
Yes, you can. That's enough for a simple drag.
Set pointer style
However, if you need to implement an element that follows the mouse and set the mouse pointer style for the drag process, you cannot use pointer-events: none to penetrate the following element, which prevents elements that need to drop from triggering related events.
So what to do?
here .setPointerCapture() Functions come in handy.
You can create a hidden element vdom to the body within the pointerdown event.
Make it the host object for the global variable, and then remove it in the up event.
Then, use vdom in the pointerdown event. SetPointerCapture (e.pointerID) to set the capture object for the current pointerEvent event to vdom
At this point, all events about the pointerID will be bound to the vdom element. The mouse pointer also follows the vdom element.
What is pointerID
The pointerID is the unique id generated by the pointerEvent event for each input point.
Mouse always has only one pointerID. Touch or pen will be generated when pointerdown, and will not be destroyed until the touch point disappears. The pointerID of events such as move enter out triggered during this period will be identical with that of down. You can easily interpret it as finger id. This is an advantage that touchEvent cannot compare.
let div = document.querySelector('#a'); div.style.cursor = 'drag';//Set Mouse Style div.addEventListener('pointerdown', (e) => { let vdom = document.body.appendChild(document.createElement('p')); vdom.setPointerCapture(e.pointerId); vdom.style.cursor = "grabbing";//Set Down Mouse Style let dragHandle=document.body.appendChild(document.createElement('div')); dragHandle.appendChild("I follow the mouse div"); dragHandle.style.position = "absolute"; dragHandle.style.pointerEvents = "none";//Event Penetration dragHandle.style.left=e.x; dragHandle.style.top=e.y; document.addEventListener('pointerup', pointerup); document.addEventListener('pointermove', pointermove); document.addEventListener('pointercancel', pointerup); function pointerup(e) { document.body.removeChild(vdom); document.removeEventListener('pointerup', pointerup); document.removeEventListener('pointermove', pointerup); document.removeEventListener('pointercancel', pointerup); } function pointermove(e) { //Update the location of the following pointer element dragHandle.style.left=e.x; dragHandle.style.top=e.y; } });
At this point, we achieve a global change in the mouse pointer during the drag process, unaffected by any other element.
The following elements we draw can also be permanently penetrated.
Analog drop
At this point, however, we have not let the drop element know that it has been dropped.
Because the setPointerCapture function is set, our other elements cannot accept any pointer events.
This is embarrassing...
Nevertheless, think about it. We just want to know if the element at the current pointer position is drop able.
That's all right. If you've used jq, you probably know bbox(), which is dom.getBoundingClientRect(); Abbreviations for.
It is used to get the absolute location information of the target element currently in the browser.
We can simulate a set of hit detection for judgment.
Hit Test
//Used to detect whether a coordinate is within the element rectangle function hitTest(dom,{ x , y }) { let bbox = dom.getBoundingClientRect(); return x > bbox.left && x < bbox.right && y > bbox.top && y < bbox.bottom; }
With this method, we can easily determine if the element under the current pointer position is a drop.
The disadvantage is that you can only judge rectangular areas. If you drag and drop an object with a different shape, you need to modify it yourself.
So we can write that...
let dropElArr = [];//Add elements that you will need to drop to this array yourself let dropHit = null; //drop elements that pass hit detection during move let dataTransfer = {};//Data to be passed to drop let div = document.querySelector('#a'); vdom.style.cursor = 'drag';//Set Mouse Style div.addEventListener('pointerdown', (e) => { let vdom = document.body.appendChild(document.createElement('p')); vdom.setPointerCapture(e.pointerId); this.vdom.style.cursor = "grabbing";//Set Down Mouse Style let dragHandle = document.body.appendChild(document.createElement('div')); dragHandle.appendChild("I follow the mouse div"); dragHandle.style.position = "absolute"; dragHandle.style.pointerEvents = "none"; dragHandle.getBoundingClientRect(); document.addEventListener('pointerup', pointerup); document.addEventListener('pointermove', pointermove); document.addEventListener('pointercancel', pointerup); dataTransfer = {};//The data you want to pass to drop function pointerup(e) { document.body.removeChild(vdom); vdom = null; document.removeEventListener('pointerup', pointerup); document.removeEventListener('pointermove', pointerup); document.removeEventListener('pointercancel', pointerup); if (dropHit) dropHit.drop(e, dataTransfer);//Notify the hit drag element drop event } function pointermove(e) { //Update the location of the following pointer element dragHandle.style.left = e.x; dragHandle.style.top = e.y; //Get the drop element hit for (let i = 0; i < dropElArr.length; i++) { if (hitTest(dropElArr[i], e)) { if(dropHit&&dropHit.dragleave)dropHit.dragleave(e,dataTransfer);//Notify drag element to drag away dropHit = dropElArr[i]; if(dropHit.dragenter)dropHit.dragenter(e,dataTransfer);//Notify drag elements to drag and drop into return; } } if(dropHit&&dropHit.dragleave){ dropHit.dragleave(e,dataTransfer);//Notify drag element to drag away dropHit = null; } } function hitTest(dom, { x, y }) { let bbox = dom.getBoundingClientRect(); return x > bbox.left && x < bbox.right && y > bbox.top && y < bbox.bottom; } });
So far, we have fulfilled all our requirements.
And as you can see in the above code, we have also achieved the transfer of data in each process.
In addition: The above code has not been run and tested in this article. There may be some faults in writing. Its logic has been successfully applied to the project. Please correct!