H5 HTML Mobile Touch drag drop Custom Drag Style Drag Scheme Simulated with PointerEvent

Posted by flashicon on Thu, 17 Feb 2022 21:08:39 +0100

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

  • 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!

Topics: Javascript Front-end html