确定从dragenter& amp; dragover事件

时间:2012-06-16 17:59:05

标签: javascript html5 draggable

我正在尝试使用HTML5可拖动的API(虽然我意识到它has its problems)。到目前为止,我遇到的唯一一个问题是,当dragoverdragenter事件触发时,我无法找到确定被拖动内容的方法:

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

我意识到我可以认为这是发起dragstart事件的最后一个元素,但是...多点触控。我还尝试使用e.dataTransfer.setData中的dragstart附加唯一标识符,但显然该数据来自dragover / dragenter {/ 3}}:

  

只有在丢弃事件发生丢弃时才能使用此数据。

那么,有什么想法吗?

更新:在撰写本文时,HTML5拖放似乎没有在任何主流移动浏览器中实现,因此在实践中提出了关于多点触控的观点。但是,我希望有一个解决方案可以保证适用于inaccessible的任何实现,这似乎不会阻止同时拖动多个元素。

我在下方发布了the spec,但这是一个丑陋的黑客攻击。我仍然希望有更好的答案。

9 个答案:

答案 0 :(得分:50)

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

dragover没有权利在拖动事件中查看数据。

此信息仅在DRAG_START和DRAG_END(丢弃)期间可用。

问题是,在你碰巧仔细阅读这些规格或地点之前,它并不是很明显并且令人抓狂。

解决方法:

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

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

这样的事情。要明确的是,“包含”并不是一个神奇的子弹,它本身可以成为一个性能问题。注意了解您的使用情况和方案。

答案 1 :(得分:20)

对我的问题的简短回答结果是:不。WHATWG spec没有提供{{1}中被拖动元素(在规范中称为“源节点”)的引用},dragenterdragover个事件。

为什么不呢?有两个原因:

首先,正如Jeffery在his comment中指出的那样,WHATWG规范是基于IE5 +的拖放实现,这种实现早于多点触控设备。 (截至撰写本文时,没有主要的多点触控浏览器实现HTML拖放。)在“单触”上下文中,可以轻松地在dragleave上存储对当前拖动元素的全局引用。 / p>

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

但无法向所有拖拽事件提供序列化信息,除了通过dragleave(我的working solution答案中所述),这是API中的一个明显遗漏。 我已经submitted a proposal for public data in drag events加入了WHATWG,我希望你能表达你的支持。

答案 2 :(得分:5)

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

此处的有效行

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

然后在dragoverdragenter事件中,e.dataTransfer.types包含字符串'draggable',这是确定拖动哪个元素所需的ID。 (请注意,浏览器显然需要为text/html等已识别的MIME类型设置数据才能实现此目的。在Chrome和Firefox中进行测试。)

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

更新:值得补充的一点是,除了不优雅之外,规范还规定所有数据类型都将转换为小写ASCII。因此请注意,涉及大写字母或unicode的选择器将会中断。 Jeffery's solution回避了这个问题。

答案 3 :(得分:4)

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

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

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

    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/

答案 4 :(得分:3)

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

var draggedElement = null;

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

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

答案 5 :(得分:3)

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

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

dragenterdragleave个事件中,找到其expando属性值与当前事件的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可拖动。您是否能够在任何触摸设备上进行拖动?如果没有,多点触摸不会成为问题,您可以将拖动的元素存储在变量中。

答案 6 :(得分:1)

要检查它是否是文件用途:

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

答案 7 :(得分:0)

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

MDN列出了一些recommended drag types,例如text / html,但如果没有合适,那么只需使用'text / html'类型将id存储为文本,或者创建自己的类型,例如'application /节点ID”。

答案 8 :(得分:-1)

我认为你可以通过致电e.relatedTarget来获取它。请参阅:http://help.dottoro.com/ljogqtqm.php

好的,我尝试了e.target.previousElementSibling并且它有效,排序.... http://jsfiddle.net/Gz8Qw/4/我认为它会挂起,因为事件被解雇了两次。一次为div,一次为文本节点(当它为文本节点触发时,它为undefined)。不确定这是否会让你到达你想去的地方......