确定哪些内容正在从 draenter & draver 事件中拖动

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

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

我意识到我可以假设它是触发 dragstart事件的最后一个元素,但是... 多点触摸。我也尝试过使用来自 dragstarte.dataTransfer.setData来连接唯一标识符,但显然这个数据是来自 dragover/dragenter无法接近:

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

有什么想法吗?

更新: 在撰写本文时,HTML5的拖放功能似乎还没有在任何主流的移动浏览器中实现,这使得多点触摸在实践中变得毫无意义。但是,我希望有一个解决方案能够保证在 说明书的任何实现中都能正常工作,它似乎不会阻止同时拖动多个元素。

我已经在下面贴出了 有效的解决办法,但它是一个丑陋的黑客。我仍然希望有一个更好的答案。

36386 次浏览

我认为你可以通过打电话 e.relatedTarget得到它,见: http://help.dottoro.com/ljogqtqm.php

好吧,我试了 e.target.previousElementSibling,它的工作,排序... 。我认为它挂断是因为该事件正在发射两次。一次用于 div,一次用于文本节点(当它触发文本节点时,它是 undefined)。不知道这能不能让你达到目的。

您可以确定在拖动开始时被拖动的是什么,并将其保存在一个变量中,以便在触发 Dragover/dragenter 事件时使用:

var draggedElement = null;


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


function dragEnter(event) {
// use the dragged element here...
};

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

这里的主动线路是

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

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

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

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

drag事件中,将 event.xevent.y复制到一个对象,并将其设置为被拖动元素上的扩展属性的值。

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

dragenterdragleave事件中,找到其扩展属性值与当前事件的 event.xevent.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/

现在,理论上这应该可行。然而,我不知道如何启用拖动触摸设备,所以我无法测试它。这个链接是移动格式的,但触摸和幻灯片并没有导致拖动开始在我的机器人。http://fiddle.jshell.net/gilly3/4bVhL/1/show/

编辑: 从我读到的内容来看,似乎任何触摸设备都不支持 HTML5的可拖动性。你能在任何触摸设备上拖动工作吗?如果没有,多点触控就不是问题,你可以只是把拖动的元素存储在一个变量中。

从我在 MDN 上看到的来看,你所做的是正确的。

MDN 列出了一些 推荐的拖动类型,比如 text/html,但是如果没有合适的,那么使用‘ text/html’类型将 id 存储为文本,或者创建自己的类型,比如‘ application/node-id’。

根据目前的规范,我认为没有任何解决方案不是“黑客攻击”。WHATWG 是解决这个问题的一种方法: -)

扩展“(非常不雅的)解决方案”(小样) :

  • 创建当前拖动的所有元素的全局散列表:

    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/

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

为什么不呢? 两个原因:

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

其次,HTML 拖放允许您在多个文档之间拖动元素。这非常棒,但也意味着在每个 dragenterdragoverdragleave事件中提供对正在拖动的元素的引用是没有意义的; 您不能在不同的文档中引用元素。这是 API 的一个优势,无论拖动源自同一个文档还是不同的文档,这些事件都以相同的方式工作。

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

我想在这里加上一个非常明确的答案,这样每个经过这里的人都能清楚地看到。在其他答案中,这句话已经说过好几次了,但是,在这里,我尽可能清楚地说明:

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

此信息仅在 DRAG _ START 和 DRAG _ END (删除)期间可用。

问题是,它根本不明显,而且令人抓狂,直到你碰巧读到足够深入的规格或地方,像这里。

解决办法:

作为一种可能的解决方案,我已经向 DataTransfer 对象添加了特殊的键,并对其进行了测试。例如,为了提高效率,我希望在拖动开始时查找一些“拖放目标”规则,而不是每次发生“拖动过度”时查找。为此,我在 dataTransfer 对象上添加了标识每个规则的键,并测试了那些带有“包含”的规则。

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

诸如此类。需要说明的是,这个“包含”并不是一个灵丹妙药,它本身可能成为一个性能问题。注意了解您的用法和场景。

检查它是否是一个文件使用:

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