【问题标题】:Determine what is being dragged from dragenter & dragover events确定从 dragenter 和 dragover 事件中拖动的内容
【发布时间】:2012-06-19 10:00:03
【问题描述】:

我正在尝试使用 HTML5 可拖动 API(尽管我意识到它has its problems)。到目前为止,我遇到的唯一阻碍是当dragoverdragenter 事件触发时,我无法确定要拖动的内容:

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

我意识到我可以假设它是触发dragstart 事件的最后一个元素,但是……多点触控。我也尝试过使用来自dragstarte.dataTransfer.setData 来附加一个唯一标识符,但显然该数据是来自dragover/dragenterinaccessible

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

那么,有什么想法吗?

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

我在下面发布了a working solution,但这是一个丑陋的黑客。我仍然希望得到更好的答案。

【问题讨论】:

标签: javascript html draggable


【解决方案1】:

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

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

此信息仅在 DRAG_START 和 DRAG_END(投放)期间可用。

问题在于,在您碰巧对规范或类似此处的地方进行了足够深入的阅读之前,它根本不明显且令人抓狂。

解决方法:

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

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

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

【讨论】:

  • 如果您不支持 IE/Edge,这是一个很好的建议。 IE/Edge 只允许两个值作为类型:“text”或“url”。
  • 解决方法甚至可以使用JSON.stringifyJSON.parse (poc) 传递更多数据。虽然很不雅
  • 我喜欢清晰的答案,在我面前大喊大叫。容易记住:D
  • 我是否正确地认为这个答案已经过时了,至少部分过时了?使用DataTransfer.items,至少可以在dragenter 事件处理程序中检查拖动文件的MIME 类型。有one answer(远在下面)提到它。
【解决方案2】:

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

为什么不呢?两个原因:

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

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

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

【讨论】:

  • 我遇到了同样的问题,并得出结论,最好的办法是在数据传输中加入自定义的 mime 类型。因为您提供的拖动功能的主要目的是停止事件,我同意它需要一些信息作为基础。出于许多目的,能够说“拖动包含应用程序/myapp,我可以处理它因此取消事件”似乎很好。
  • 我已经看过你的提案链接,但我真的不知道在哪里可以看到这个答案,甚至表达我的支持
【解决方案3】:

一个(非常不优雅的)解决方案是将选择器作为 type 的数据存储在 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's solution 回避了这个问题。

【讨论】:

  • 是的,面对转小写的钥匙有一段时间了:(
【解决方案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/

【讨论】:

  • 哎呀,我没想到同一个元素可以同时拖动多次。该规范似乎并未排除它。
  • @TrevorBurnham 我已经更新了我的答案,允许在同一个元素上进行多次拖动,但老实说,我不知道规范将如何改变以允许多点触控。
【解决方案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

【讨论】:

  • 我认为originalEvent 在大多数情况下都不会被填充,即使对于 Chrome。
  • @windmaomao 我用 Chrome、Edge 和 Firefox 对其进行了测试,它适用于所有人。
【解决方案6】:

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

var draggedElement = null;

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

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

【讨论】:

  • 我在我的问题中提到了这一点。这种方法在可以同时拖动多个元素的多点触控设备上是站不住脚的。
【解决方案7】:

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/

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

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

【讨论】:

  • @Trevor - 真的吗? My jsfiddle 在 Chrome 中为我工作。
  • @gilly3 你的方法是如何工作的?我没有使用多点触控设备,但我认为您可以同时拖动多个对象。最后调用drag事件的元素是否保证是第一个调用drag enter事件的元素? (我的措辞不是最清楚,但希望你明白我的意思)
  • @Jules - 我会期望在多点触控环境(支持 HTML5 可拖动)中为您建议的相同元素立即连续调用 drag 和 dragenter ,如果不是这样,我会感到惊讶。 但是, 如果可以保证(即由规范以这种方式定义),我会更加惊讶。我的方法保留了一组被拖动的元素,并搜索与当前事件位于同一位置的元素。我知道每个拖动元素的位置,因为我在 drag 事件处理程序中更新了元素的自定义属性。
  • @gilly3 我明白你的意思了。考虑一下,只有在拖动之后才会直接检查 dragenter,因此在检查任何其他拖动之前没有理由不立即调用 dragenter。
  • @gilly3 该规范确实保证drag 总是在dragenter 之前触发(它没有触发我的事实是由于非常特定版本的Chrome 中的一个错误)。但是,它不能保证xy 将具有一致的值。总的来说,这是一个聪明的回应,我给了它一个 +1,但我认为我不能接受它作为答案。
【解决方案8】:

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

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

【讨论】:

  • 正如我在问题中所说:您可以尝试将 id 存储为 text/html 数据,但由于安全模型,您无法访问该数据,直到删除。请参阅the spec,尤其是。 “保护模式。”
  • 对不起,我错过了!如果可以放下,这是为了提供视觉提示吗?您至少可以尝试/赶上 dragenter 事件吗?当涉及到丢弃时,您拥有所有可用的信息,如果不适用,您可以取消丢弃。
【解决方案9】:

我想你可以通过拨打 e.relatedTarget 来获取它,请参阅:http://help.dottoro.com/ljogqtqm.php

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

【讨论】:

  • 不。 Try it for yourself。在 Chrome 中,e.relatedTargetnull。在 Firefox 中,XrayWrapper 有点奇怪。
  • 我没有得到这些结果(对我来说它返回了#dragTarget),但我意识到这仍然不是您想要的。
  • 修正我之前的评论:XrayWrapper 是 Firefox 开发工具的一个怪癖,只要你 console.log 一个 DOM 元素就会出现。无论如何,它包裹的是被鼠标悬停的东西,而不是可拖动的东西。
  • e.target.previousElementSibling 仅起作用,因为draggable 是拖动目标之前的元素...
  • 啊,我明白了,我在不同的背景下思考“以前”。我现在看到它实际上是 DOM 中的前一个元素。我很高兴你指出了所有这些(而不是只留下空白),帮助我理解 - ty。
猜你喜欢
  • 2015-12-17
  • 1970-01-01
  • 1970-01-01
  • 2014-02-25
  • 2015-11-02
  • 2010-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多