确定从Dragenter和Dragover事件中拖动的内容


74

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

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

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

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

那么,有什么想法吗?

更新:在撰写本文时,似乎没有在任何主要的移动浏览器中实现HTML5拖放,这实际上是关于多点触控的。但是,我希望有一个解决方案能够保证在该规范的任何实现中都可以使用,但这似乎并不妨碍同时拖动多个元素。

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


1
通常,您应该能够使用函数内部的“ this”属性,该属性链接到触发事件的对象。
lgomezma 2012年

2
@lgomezma不,thisdragenter/dragover事件处理程序中指向要拖动的元素,而不是要拖动的元素。等于e.target。示例:jsfiddle.net/TrevorBurnham/jWCSB
Trevor Burnham

我不认为Microsoft在最初为IE 5设计和实现拖放时会想到多点触控。
Jeffery To

@JefferyTo我知道这一点,但是现在他们的设计已被编入标准,我希望通过遵循WHATWG规范(而不仅仅是现有实现)来面向未来。
Trevor Burnham 2012年

Answers:


71

我想在这里添加一个非常明确的答案,以便所有徘徊在这里的人都可以看到。在其他答案中已经说过几次了,但是在这里,我可以清楚地做到:

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

此信息仅在DRAG_START和DRAG_END(删除)期间可用。

问题在于,直到您碰巧对规范或此处的地方进行了足够深入的阅读后,问题才变得显而易见并发疯。

周围的工作:

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

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

这样的事情。需要明确的是,“包含”并不是万能的,它本身也可能成为性能问题。注意了解您的用法和方案。


1
如果您不支持IE / Edge,这是一个很好的建议。IE / Edge仅允许两个值作为类型:“文本”或“ URL”。
wawka '16

解决方法甚至可以使用JSON.stringifyJSON.parsepoc)传递更多数据。非常不雅虽然
雅典BN

3
我喜欢清晰的答案在我的脸上喊。容易记住:D
Wilt

24

简短的回答我的问题真可谓是:否。WHATWG规范不提供的一个参考要素被拖动(称为规范“源节点”) dragenterdragoverdragleave事件。

为什么不?两个原因:

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

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

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


我遇到了同样的问题,得出的结论是,最好的选择是在数据传输中保留自定义的mime类型。因为您提供的拖曳功能的主要目的是停止事件,所以我同意它需要一些信息来作为基础。对于许多目的,可以说“拖动包含application / myapp,我可以处理,因此取消该事件”似乎很好。
伍迪

1
我看过您的提案链接,但我真的不知道该在哪里阅读答案,甚至表示支持
Ulysse BN

11

(非常优雅的)解决方案是将选择器存储为对象中的一种数据类型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的选择器将中断。杰弗里的解决方案避开了这个问题。


是的,在很短一段时间内都面临着将钥匙变成小写的关键:(
$$

6

根据当前的规范,我认为没有任何解决方案不是“ hack”的。请愿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/


哎呀,我没有想到可以将同一元素同时拖动多次。该规范似乎并不排除它。
Trevor Burnham 2012年

@TrevorBurnham我已经更新了答案,以允许在同一元素上多次拖动,但老实说,我不知道规格将如何更改以允许多点触摸。
Jeffery To

4

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

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

@windmaomao我在Chrome,Edge和Firefox上进行了测试,并且对所有人都适用。
user2272143

3

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

var draggedElement = null;

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

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

我在问题中提到了这一点。这种方法在多点触摸设备上是站不住脚的,在多点触摸设备上可以同时拖动多个元素。
Trevor Burnham 2012年

3

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

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

dragenterdragleave事件中,找到其expando属性值与当前事件的event.x和相匹配的元素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可拖动。您是否可以在任何触摸设备上进行拖动操作?如果不是这样,那么多点触摸就不会成为问题,您可以求助于将拖动的元素存储在变量中。


@Trevor-真的吗? 我的jsfiddle在Chrome中为我工作。
gilly3 2012年

@ gilly3您的方法如何工作?我没有使用多点触控设备,但我想您可以同时拖动多个对象。最后调用拖动事件的元素是否一定是首先调用拖动输入事件的元素?(我的措词不是最清楚的,但希望您能理解我的意思)
Jules

@Jules-我希望可以在多点触控环境(支持HTML5可拖动)中为与您建议的相同的元素立即连续调用drag和dragenter,如果不是这样,我会感到惊讶。但是,如果能够保证(即规范中定义的那样)我会感到惊讶。我的方法保留拖动元素的数组,并搜索与当前事件位于相同位置的元素。我知道每个拖动的元素在什么位置,因为我在drag事件处理程序中更新了该元素上的自定义属性。
gilly3 2012年

@ gilly3我明白你的意思了。考虑到这一点,仅在拖动之后立即检查Dragenter,因此在检查任何其他拖动之前没有理由不立即调用Dragenter。
Jules 2012年

@ gilly3规范确实可以确保drag总是在之前触发dragenter(之所以没有触发我,是因为特定版本的Chrome中存在错误)。然而,这并不能保证x,并y会有一致的价值观。总的来说,这是一个聪明的回应,我给它+1,但是我认为我不能接受它作为答案。
Trevor Burnham 2012年

0

根据我对MDN的了解,您所做的是正确的。

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


就像我在问题中说的那样:您可以尝试将id存储为text/html数据,但是由于安全性模型的原因,直到删除数据后才能访问该数据。参见规格,特别是。“保护模式。”
Trevor Burnham 2012年

抱歉,我错过了!如果可以放下,是否可以提供视觉提示?您至少可以尝试/抓住Dragenter事件。关于删除,您可以使用所有可用信息,然后取消删除(如果不适用)。
朱尔斯2012年

-1

我认为您可以致电e.relatedTarget 以下网址获得它:http : //help.dottoro.com/ljogqtqm.php

好的,我尝试了一下,e.target.previousElementSibling并且可以正常工作。... http : //jsfiddle.net/Gz8Qw/4/我认为由于事件被触发两次而挂断了。一次用于div,一次用于文本节点(为文本节点触发时为undefined)。不确定是否能将您带到您想去的地方...


不。自己尝试一下。在Chrome中,e.relatedTargetnull。在Firefox中,这有些奇怪XrayWrapper
Trevor Burnham 2012年

我没有得到这些结果(对我来说,它返回了#dragTarget),但是我意识到这仍然不是您想要的。
solidau 2012年

修改我以前的评论: XrayWrapper是Firefox开发工具的古怪之处,它在出现console.logDOM元素时就会出现。无论如何,它所包裹的是鼠标悬停的东西,而不是可拖动的东西。
Trevor Burnham 2012年

1
e.target.previousElementSibling仅起作用,因为draggable元素是拖动目标之前的元素...
Trevor Burnham

嗯,我明白了,我在不同的背景下思考“以前的”。我现在看到它实际上是DOM中的先前元素。我很高兴您能指出所有这些信息(而不是只留下空白),并帮助我理解-ty。
solidau 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.