Determine what is being dragged from dragenter & d

2019-01-13 12:10发布

I'm trying to use the HTML5 draggable API (though I realize it has its problems). So far, the only showstopper I've encountered is that I can't figure out a way to determine what is being dragged when a dragover or dragenter event fires:

el.addEventListener('dragenter', function(e) {
  // what is the draggable element?
});

I realize I could assume that it's the last element to fire a dragstart event, but... multitouch. I've also tried using e.dataTransfer.setData from the dragstart to attach a unique identifier, but apparently that data is inaccessible from dragover/dragenter:

This data will only be available once a drop occurs during the drop event.

So, any ideas?

Update: As of this writing, HTML5 drag-and-drop does not appear to be implemented in any major mobile browser, making the point about multitouch moot in practice. However, I'd like a solution that's guaranteed to work across any implementation of the spec, which does not appear to preclude multiple elements from being dragged simultaneously.

I've posted a working solution below, but it's an ugly hack. I'm still hoping for a better answer.

9条回答
Fickle 薄情
2楼-- · 2019-01-13 12:24

The short answer to my question turns out to be: No. The WHATWG spec doesn't provide a reference to the element being dragged (called the "source node" in the spec) in the dragenter, dragover, or dragleave events.

Why not? Two reasons:

First, as Jeffery points out in his comment, the WHATWG spec is based on IE5+'s implementation of drag-and-drop, which predated multi-touch devices. (As of this writing, no major multi-touch browser implements HTML drag-and-drop.) In a "single-touch" context, it's easy to store a global reference to the current dragged element on dragstart.

Second, HTML drag-and-drop allows you to drag elements across multiple documents. This is awesome, but it also means that providing a reference to the element being dragged in every dragenter, dragover, or dragleave event wouldn't make sense; you can't reference an element in a different document. It's a strength of the API that those events work the same way whether the drag originated in the same document or a different one.

But the inability to provide serialized information to all drag events, except through dataTransfer.types (as described in my working solution answer), is a glaring omission in the API. I've submitted a proposal for public data in drag events to the WHATWG, and I hope you'll express your support.

查看更多
别忘想泡老子
3楼-- · 2019-01-13 12:24

A (very inelegant) solution is to store a selector as a type of data in the dataTransfer object. Here is an example: http://jsfiddle.net/TrevorBurnham/eKHap/

The active lines here are

e.dataTransfer.setData('text/html', 'foo');
e.dataTransfer.setData('draggable', '');

Then in the dragover and dragenter events, e.dataTransfer.types contains the string 'draggable', which is the ID needed to determine which element is being dragged. (Note that browsers apparently require data to be set for a recognized MIME type like text/html as well in order for this to work. Tested in Chrome and Firefox.)

It's an ugly, ugly hack, and if someone can give me a better solution, I'll happily grant them the bounty.

Update: One caveat worth adding is that, in addition to being inelegant, the spec states that all data types will be converted to lower-case ASCII. So be warned that selectors involving capital letters or unicode will break. Jeffery's solution sidesteps this issue.

查看更多
唯我独甜
4楼-- · 2019-01-13 12:25

You can determine what is being dragged when the drag starts and save this in a variable to use when the dragover/dragenter events are fired:

var draggedElement = null;

function drag(event) {
    draggedElement = event.srcElement || event.target;
};

function dragEnter(event) {
    // use the dragged element here...
};
查看更多
Root(大扎)
5楼-- · 2019-01-13 12:27

I think you can get it by calling e.relatedTarget See: http://help.dottoro.com/ljogqtqm.php

OK, I tried e.target.previousElementSibling and it works, sorta.... http://jsfiddle.net/Gz8Qw/4/ I think it hangs up because the event is being fired twice. Once for the div and once for the text node (when it fires for text node, it is undefined). Not sure if that will get you where you want to be or not...

查看更多
Explosion°爆炸
6楼-- · 2019-01-13 12:31

Given the current spec, I don't think there is any solution that isn't a "hack". Petitioning the WHATWG is one way to get this fixed :-)

Expanding on the "(very inelegant) solution" (demo):

  • Create a global hash of all elements currently being dragged:

    var dragging = {};
    
  • In the dragstart handler, assign a drag ID to the element (if it doesn't have one already), add the element to the global hash, then add the drag ID as a data type:

    var dragId = this.dragId;
    
    if (!dragId) {
        dragId = this.dragId = (Math.random() + '').replace(/\D/g, '');
    }
    
    dragging[dragId] = this;
    
    e.dataTransfer.setData('text/html', dragId);
    e.dataTransfer.setData('dnd/' + dragId, dragId);
    
  • In the dragenter handler, find the drag ID among the data types and retrieve the original element from the global hash:

    var types = e.dataTransfer.types, l = types.length, i = 0, match, el;
    
    for ( ; i < l; i++) {
        match = /^dnd\/(\w+)$/.exec(types[i].toLowerCase());
    
        if (match) {
            el = dragging[match[1]];
    
            // do something with el
        }
    }
    

If you keep the dragging hash private to your own code, third-party code would not be able to find the original element, even though they can access the drag ID.

This assumes that each element can only be dragged once; with multi-touch I suppose it would be possible to drag the same element multiple times using different fingers...


Update: To allow for multiple drags on the same element, we can include a drag count in the global hash: http://jsfiddle.net/jefferyto/eKHap/2/

查看更多
孤傲高冷的网名
7楼-- · 2019-01-13 12:39

From what I have read on MDN, what you are doing is correct.

MDN lists some recommended drag types, such as text/html, but if none are suitable then just store the id as text using the 'text/html' type, or create your own type, such as 'application/node-id'.

查看更多
登录 后发表回答