80

我正在尝试使用 HTML5 可拖动 API(尽管我意识到它有问题)。到目前为止,我遇到的唯一障碍是我无法找到一种方法来确定当一个dragoverordragenter事件触发时正在拖动什么:

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

我意识到我可以假设它是触发dragstart事件的最后一个元素,但是......多点触控。我也尝试使用e.dataTransfer.setDatafromdragstart来附加唯一标识符,但显然无法dragover/访问数据dragenter

只有在 drop 事件期间发生 drop 时,此数据才可用。

那么,有什么想法吗?

更新:在撰写本文时,HTML5 拖放似乎没有在任何主要的移动浏览器中实现,这使得多点触控在实践中没有实际意义。但是,我想要一个保证在 spec的任何实现中都能工作的解决方案,这似乎并不排除同时拖动多个元素。

我在下面发布了一个可行的解决方案,但这是一个丑陋的黑客。我仍然希望得到更好的答案。

4

9 回答 9

77

我想在这里添加一个非常明确的答案,以便每个路过这里的人都清楚。在其他答案中已经多次说过,但在这里,我可以说得很清楚:

dragover无权查看拖动事件中的数据。

此信息仅在 DRAG_START 和 DRAG_END(下降)期间可用。

问题是它一点也不明显而且令人抓狂,直到你碰巧对规范或像这里这样的地方进行了足够深入的阅读。

解决方法:

作为一种可能的解决方法,我向 DataTransfer 对象添加了特殊键并对其进行了测试。例如,为了提高效率,我想在拖动开始时查找一些“放置目标”规则,而不是每次发生“拖动”时查找。为此,我将标识每个规则的键添加到 dataTransfer 对象上,并使用“包含”测试了这些键。

ev.originalEvent.dataTransfer.types.includes("allow_drop_in_non_folders")

诸如此类的事情。需要明确的是,“包含”不是灵丹妙药,它本身可能会成为性能问题。请注意了解您的使用情况和场景。

于 2014-05-01T20:36:59.710 回答
25

对我的问题的简短回答是:不。WHATWG 规范没有提供对在 、 或 事件中被拖动的元素(在规范中称为“源节点”)dragenterdragover引用dragleave

为什么不?两个原因:

首先,正如 Jeffery 在他的评论中指出的那样,WHATWG 规范基于 IE5+ 的拖放实现,该实现早于多点触控设备。(在撰写本文时,没有主要的多点触控浏览器实现 HTML 拖放。)在“单点触控”上下文中,很容易将当前拖动元素的全局引用存储在dragstart.

其次,HTML 拖放允许您跨多个文档拖动元素。这太棒了,但这也意味着提供对每个 、 或 事件中拖动的元素的引用是dragenter没有dragover意义dragleave的;您不能引用不同文档中的元素。API 的优势在于,无论拖动是源自同一个文档还是不同的文档,这些事件的工作方式都是相同的。

dataTransfer.types但是,除了通过(如我的工作解决方案答案中所述)之外,无法为所有拖动事件提供序列化信息是 API 中明显的遗漏。我已经向 WHATWG提交了关于拖动事件中的公共数据的提案,希望您能表示支持。

于 2012-06-25T16:29:30.670 回答
14

一个(非常不优雅的)解决方案是将选择器作为一种数据类型存储在dataTransfer对象中。这是一个例子:http: //jsfiddle.net/TrevorBurnham/eKHap/

这里的活动线路是

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

然后在dragoveranddragenter事件中,e.dataTransfer.types包含 string 'draggable',这是确定正在拖动哪个元素所需的 ID。(请注意,浏览器显然也需要为可识别的 MIME 类型设置数据才能text/html使其正常工作。在 Chrome 和 Firefox 中测试。)

这是一个丑陋,丑陋的黑客,如果有人能给我一个更好的解决方案,我会很高兴地给他们赏金。

更新:一个值得补充的警告是,除了不优雅之外,规范声明所有数据类型都将转换为小写 ASCII。因此请注意,涉及大写字母或 unicode 的选择器会损坏。Jeffery 的解决方案回避了这个问题。

于 2012-06-18T19:21:26.083 回答
6

鉴于当前的规范,我认为没有任何解决方案不是“黑客”。向 WHATWG 提出请愿是解决此问题的一种方法 :-)

扩展“(非常不优雅的)解决方案”(演示):

  • 创建当前被拖动的所有元素的全局哈希:

    var dragging = {};
    
  • dragstart处理程序中,为元素分配一个拖动 ID(如果它还没有),将元素添加到全局哈希,然后将拖动 ID 添加为数据类型:

    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);
    
  • dragenter处理程序中,在数据类型中找到拖动 ID,并从全局哈希中检索原始元素:

    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
        }
    }
    

如果您将dragging哈希对您自己的代码保密,第三方代码将无法找到原始元素,即使他们可以访问拖动 ID。

这假设每个元素只能拖动一次;使用多点触控,我想可以使用不同的手指多次拖动相同的元素......


更新:为了允许对同一元素进行多次拖动,我们可以在全局哈希中包含拖动计数:http: //jsfiddle.net/jefferyto/eKHap/2/

于 2012-06-20T18:07:34.973 回答
5

要检查它是否是文件,请使用:

e.originalEvent.dataTransfer.items[0].kind

要检查类型,请使用:

e.originalEvent.dataTransfer.items[0].type

即我只想允许一个文件 jpg, png, gif, bmp

var items = e.originalEvent.dataTransfer.items;
var allowedTypes = ["image/jpg", "image/png", "image/gif", "image/bmp"];
if (items.length > 1 || items["0"].kind != "file" || items["0"].type == "" || allowedTypes.indexOf(items["0"].type) == -1) {
    //Type not allowed
}

参考:https ://developer.mozilla.org/it/docs/Web/API/DataTransferItem

于 2017-08-02T21:14:43.623 回答
3

您可以确定拖动开始时正在拖动的内容,并将其保存在一个变量中,以便在触发 dragover/dragenter 事件时使用:

var draggedElement = null;

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

function dragEnter(event) {
    // use the dragged element here...
};
于 2012-06-18T18:53:38.243 回答
3

在这种情况下,将和drag复制到一个对象并将其设置为拖动元素上的 expando 属性的值。event.xevent.y

function drag(e) {
    this.draggingAt = { x: e.x, y: e.y };
}

dragenteranddragleave事件中查找其 expando 属性值与当前事件的event.xand匹配的元素。event.y

function dragEnter(e) {
    var draggedElement = dragging.filter(function(el) {
        return el.draggingAt.x == e.x && el.draggingAt.y == e.y;
    })[0];
}

为了减少需要查看的元素数量,您可以通过将元素添加到数组或在事件中分配一个类并在dragstart事件中撤消它来跟踪元素dragend

var dragging = [];
function dragStart(e) {
    e.dataTransfer.setData('text/html', '');
    dragging.push(this);
}
function dragEnd(e) {
    dragging.splice(dragging.indexOf(this), 1);
}

http://jsfiddle.net/gilly3/4bVhL/

现在,理论上这应该有效。但是,我不知道如何为触摸设备启用拖动功能,因此无法对其进行测试。此链接是移动格式的,但触摸和滑动并没有导致在我的 android 上开始拖动。 http://fiddle.jshell.net/gilly3/4bVhL/1/show/

编辑:根据我的阅读,任何触摸设备都不支持 HTML5 可拖动。您是否能够在任何触摸设备上进行可拖动工作?如果没有,多点触控将不是问题,您可以仅将拖动的元素存储在变量中。

于 2012-06-19T02:06:35.333 回答
0

根据我在 MDN 上阅读的内容,您所做的是正确的。

MDN 列出了一些推荐的拖动类型,例如 text/html,但如果没有合适的,则只需使用 'text/html' 类型将 id 存储为文本,或者创建自己的类型,例如 'application/node-id'。

于 2012-06-19T10:56:53.203 回答
-1

我想你可以通过调用e.relatedTarget 看到它:http: //help.dottoro.com/ljogqtqm.php

好的,我试过e.target.previousElementSibling了,它可以工作,有点...... http://jsfiddle.net/Gz8Qw/4/我认为它挂断了,因为事件被触发了两次。一次用于 div,一次用于文本节点(当它为文本节点触发时,它是undefined)。不确定这是否会让你到达你想去的地方......

于 2012-06-18T18:41:37.590 回答