在拖动子元素时触发父元素的“dragleave”

概观

我有以下HTML结构,并将dragenterdragleave事件附加到<div id="dropzone">元素。

 <div id="dropzone"> <div id="dropzone-content"> <div id="drag-n-drop"> <div class="text">this is some text</div> <div class="text">this is a container with text and images</div> </div> </div> </div> 

问题

当我通过<div id="dropzone">拖动一个文件时, dragenter事件按预期被触发。 但是,当我将鼠标移动到像<div id="drag-n-drop">类的子元素上时,为<div id="drag-n-drop">元素触发dragenter事件,然后dragleave事件是为<div id="dropzone">元素触发的。

如果我再次将鼠标hover在<div id="dropzone">元素上,则dragenter事件再次被触发,这很酷,但是随后为刚刚离开的子元素触发dragleave事件,所以执行removeClass指令,即不酷。

这种行为有两个原因是有问题的:

  1. 我只是将dragenterdragleave<div id="dropzone">所以我不明白为什么儿童元素也附加了这些事件。

  2. 我仍在拖动<div id="dropzone">元素,同时将dragleavehover在它的子元素上,所以我不想让dragleave发射!

的jsfiddle

这是一个jsFiddle修补: http : //jsfiddle.net/yYF3S/2/

所以…我怎么能这样做,当我拖动一个文件通过<div id="dropzone">元素,即使我拖动dragleave元素, dragleave不会触发…它应该只有在离开<div id="dropzone">元素时才会触发…在元素边界内的任何位置hover/拖动不应触发dragleave事件。

我需要这是跨浏览器兼容的,至less在支持HTML5拖放的浏览器中,所以这个答案是不够的。

Google和Dropbox似乎已经知道了这一点,但是他们的源代码被缩小/复杂了,所以我没有能够从他们的实现中得出这个结论。

如果您不需要将事件绑定到子元素,则始终可以使用pointer-events属性。

 .child-elements { pointer-events: none; } 

我终于find了一个我很满意的解决scheme。 我实际上find了几种方法来做我想做的事情,但是没有一个能够像当前的解决scheme一样成功……在一种解决scheme中,由于向#dropzone元素添加/删除边框,我经历了频繁的闪烁#dropzone ……另一种方法是,如果您将鼠标hover在浏览器上,边框永远不会被移除。

无论如何,我最好的解决办法是这样的:

 var dragging = 0; attachEvent(window, 'dragenter', function(event) { dragging++; $(dropzone).addClass('drag-n-drop-hover'); event.stopPropagation(); event.preventDefault(); return false; }); attachEvent(window, 'dragover', function(event) { $(dropzone).addClass('drag-n-drop-hover'); event.stopPropagation(); event.preventDefault(); return false; }); attachEvent(window, 'dragleave', function(event) { dragging--; if (dragging === 0) { $(dropzone).removeClass('drag-n-drop-hover'); } event.stopPropagation(); event.preventDefault(); return false; }); 

这工作相当好,但问题出现在Firefox,因为Firefox是双调dragenter所以我的计数器closures。 但是,它不是一个非常优雅的解决scheme。

然后我偶然发现了这个问题: 如何在Firefox窗口外拖动时检测Firefox中的dragleave事件

所以我拿出了答案 ,并将其应用于我的情况:

 $.fn.dndhover = function(options) { return this.each(function() { var self = $(this); var collection = $(); self.on('dragenter', function(event) { if (collection.size() === 0) { self.trigger('dndHoverStart'); } collection = collection.add(event.target); }); self.on('dragleave', function(event) { /* * Firefox 3.6 fires the dragleave event on the previous element * before firing dragenter on the next one so we introduce a delay */ setTimeout(function() { collection = collection.not(event.target); if (collection.size() === 0) { self.trigger('dndHoverEnd'); } }, 1); }); }); }; $('#dropzone').dndhover().on({ 'dndHoverStart': function(event) { $('#dropzone').addClass('drag-n-drop-hover'); event.stopPropagation(); event.preventDefault(); return false; }, 'dndHoverEnd': function(event) { $('#dropzone').removeClass('drag-n-drop-hover'); event.stopPropagation(); event.preventDefault(); return false; } }); 

这是干净和优雅的,似乎在我到目前为止testing的每个浏览器(尚未testingIE)工作。

这是一个有点丑,但它的作品该死!

在你的“dragenter”处理程序中存储event.target(在你的闭包中的一个variables,或其他),然后在你的“dragleave”处理程序中,只有在你存储的event.target ===时才会激发你的代码。

如果你的“dragenter”在你不想要的时候(即,当它离开子元素后进入),那么最后一次它在鼠标离开父元素之前触发,它在父元素上,所以父元素总是在打算“dragleave”之前的最后的“dragenter”。

 (function () { var droppable = $('#droppable'), lastenter; droppable.on("dragenter", function (event) { lastenter = event.target; droppable.addClass("drag-over"); }); droppable.on("dragleave", function (event) { if (lastenter === event.target) { droppable.removeClass("drag-over"); } }); }()); 

起初,我同意丢弃pointer-events: none办法。 但后来我问自己:

你是否真的需要指针事件来处理拖拽的子元素?

在我的情况下,我有很多东西在儿童身上,例如hover以显示button以进行其他操作,内联编辑等等。但是, 拖动过程中 没有必要或者甚至不需要这些。

在我的情况下,我使用这样的事情有select地closures父容器的所有子节点的指针事件:

  div.drag-target-parent-container.dragging-in-progress * { pointer-events: none; } 

使用你最喜欢的方法来添加/删除dragEnter / dragLeave事件处理程序中dragging-in-progress的类,正如我在dragStart所做的那样,也是一样。 人。

这似乎是一个Chrome的错误。

我能想到的唯一解决方法是创build一个透明的覆盖元素来捕获你的事件: http : //jsfiddle.net/yYF3S/10/

JS

 $(document).ready(function() { var dropzone = $('#overlay'); dropzone.on('dragenter', function(event) { $('#dropzone-highlight').addClass('dnd-hover'); }); dropzone.on('dragleave', function(event) { $('#dropzone-highlight').removeClass('dnd-hover'); }); });​ 

HTML

 <div id="dropzone-highlight"> <div id="overlay"></div> <div id="dropzone" class="zone"> <div id="drag-n-drop"> <div class="text1">this is some text</div> <div class="text2">this is a container with text and images</div> </div> </div> </div> <h2 draggable="true">Drag me</h2>​ 

问题是,你在dropzones内的元素当然是dropzone的一部分,当你进入孩子,你离开父母。 解决这个并不容易。 您也可以尝试向孩子添加事件,并将其重新添加到父级。

 $("#dropzone,#dropzone *").on('dragenter', function(event) { // add a class to #dropzone event.stopPropagation(); // might not be necessary event.preventDefault(); return false; }); 

你的事件仍然会发射多次,但没有人会看到。

//编辑:使用dragmove事件永久覆盖dragleave事件:

 $("#dropzone,#dropzone *").on('dragenter dragover', function(event) { // add a class to #dropzone event.stopPropagation(); // might not be necessary event.preventDefault(); return false; }); 

只为dropzone定义dragleave事件。

如果您使用jQuery,请查看: https : //github.com/dancork/jquery.event.dragout

这真的很棒。

创build特殊事件来处理真正的dragleavefunction。

HTML5 dragleave事件更像mouseout。 这个插件是为了在拖动时复制mouseleave风格的function而创build的。

用法示例:

$('#myelement')。on('dragout',function(event){// YOUR CODE});

编辑:其实,我不认为它是依赖于jQuery,你可能只是使用代码,即使没有它。

正如benr在这个答案中所提到的,你可以防止子节点触发事件,但是如果你需要绑定一些事件,可以这样做:

 #dropzone.dragover *{ pointer-events: none; } 

并将其添加到您的JS代码中:

 $("#dropzone").on("dragover", function (event) { $("#dropzone").addClass("dragover"); }); $("#dropzone").on("dragleave", function (event) { $("#dropzone").removeClass("dragover"); }); 

@hristo我有一个更优雅的解决scheme。 检查,如果这是你可以使用的东西。

毕竟你的努力没有被浪费掉。 我设法首先使用你的,但在FF,Chrome中有不同的问题。 花了那么多小时后,我得到了这个build议,完全按照预期工作。

这是实现的方式。 我也利用视觉线索正确地引导用户关于放置区。

 $(document).on('dragstart dragenter dragover', function(event) { // Only file drag-n-drops allowed, http://jsfiddle.net/guYWx/16/ if ($.inArray('Files', event.originalEvent.dataTransfer.types) > -1) { // Needed to allow effectAllowed, dropEffect to take effect event.stopPropagation(); // Needed to allow effectAllowed, dropEffect to take effect event.preventDefault(); $('.dropzone').addClass('dropzone-hilight').show(); // Hilight the drop zone dropZoneVisible= true; // http://www.html5rocks.com/en/tutorials/dnd/basics/ // http://api.jquery.com/category/events/event-object/ event.originalEvent.dataTransfer.effectAllowed= 'none'; event.originalEvent.dataTransfer.dropEffect= 'none'; // .dropzone .message if($(event.target).hasClass('dropzone') || $(event.target).hasClass('message')) { event.originalEvent.dataTransfer.effectAllowed= 'copyMove'; event.originalEvent.dataTransfer.dropEffect= 'move'; } } }).on('drop dragleave dragend', function (event) { dropZoneVisible= false; clearTimeout(dropZoneTimer); dropZoneTimer= setTimeout( function(){ if( !dropZoneVisible ) { $('.dropzone').hide().removeClass('dropzone-hilight'); } }, dropZoneHideDelay); // dropZoneHideDelay= 70, but anything above 50 is better }); 

我的两个美分:在你的dropzone上隐藏一个图层,然后在你绘制的时候显示它,然后把dragleave作为目标。

演示: https : //jsfiddle.net/t6q4shat/

HTML

 <div class="drop-zone"> <h2 class="drop-here">Drop here</h2> <h2 class="drop-now">Drop now!</h2> <p>Or <a href="#">browse a file</a></p> <div class="drop-layer"></div> </div> 

CSS

 .drop-zone{ padding:50px; border:2px dashed #999; text-align:center; position:relative; } .drop-layer{ display:none; position:absolute; top:0; left:0; bottom:0; right:0; z-index:5; } .drop-now{ display:none; } 

JS

 $('.drop-zone').on('dragenter', function(e){ $('.drop-here').css('display','none'); $('.drop-now').css('display','block'); $(this).find('.drop-layer').css('display','block'); return false; }); $('.drop-layer').on('dragleave', function(e){ $('.drop-here').css('display','block'); $('.drop-now').css('display','none'); $(this).css('display','none'); return false; }); 

所以对于我的方法pointer-events: none; 没有太好的工作…所以这里是我的替代解决scheme:

  #dropzone { position: relative; } #dropzone(.active)::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; content: ''; } 

这样就不可能dragleave父母(在孩子dragleave )或者dragover孩子的元素。 希望这可以帮助 :)

*当我dragleavedragleave时,添加'.active'类。 但是,如果你没有这个工作,就离开这个class。

超级简单的快速解决这个,没有广泛的testing,但在Chrome现在工作。

请原谅你的咖啡。

  dragEndTimer = no document.addEventListener 'dragover', (event) -> clearTimeout dragEndTimer $('body').addClass 'dragging' event.preventDefault() return no document.addEventListener 'dragenter', (event) -> $('section').scrollTop(0) clearTimeout dragEndTimer $('body').addClass 'dragging' document.addEventListener 'dragleave', (event) -> dragEndTimer = setTimeout -> $('body').removeClass 'dragging' , 50 

这修复了Chrome闪烁错误,或者至less是导致我的问题的排列。

在这里,最简单的解决scheme之一●︿●

看看这个小提琴 < – 尝试拖动框内的一些文件

你可以做这样的事情:

 var dropZone= document.getElementById('box'); var dropMask = document.getElementById('drop-mask'); dropZone.addEventListener('dragover', drag_over, false); dropMask.addEventListener('dragleave', drag_leave, false); dropMask.addEventListener('drop', drag_drop, false); 

由此,你应该已经知道这里发生了什么。
只要看看小提琴,你知道。

我有一个类似的问题,我这样解决:

问题:当用户在“拖放区域”(ul元素)中放置一个元素时,会触发函数drop(ev),但不幸的是,当元素被放入其子元素之一(li元素)时也会触发该元素。

修正:

 function drop(ev) { ev.preventDefault(); data=ev.dataTransfer.getData('Text'); if(ev.target=="[object HTMLLIElement]") {ev.target.parentNode.appendChild(document.getElementById(data));} else{ev.target.appendChild(document.getElementById(data));} } 

我试图自己实现这个file upload框,当用户将文件拖入空间时,框的颜色会改变。

我发现一个解决scheme,是一个很好的Javascript和CSS的组合。 假设你有一个带有id #drop div的droppable区域。 添加到您的Javascript:

 $('#drop').on('dragenter', function() { $(this).addClass('dragover'); $(this).children().addClass('inactive'); }); $('#drop').on('dragleave', function() { $(this).removeClass('dragover'); $(this).children().removeClass('inactive'); }); 

然后,把这个添加到你的CSS,去激活所有的孩子将class .inactive

 #drop *.inactive { pointer-events: none; } 

因此,只要用户在框上拖动元素,子元素将不活动。

我对这里提到的任何解决方法都不满意,因为我不想失去对孩子元素的控制。

所以我使用了不同的逻辑方法,将它翻译成一个名为jquery-draghandler的jQuery插件。 它绝对不会操纵DOM,保证高性能。 它的使用很简单:

 $(document).ready(function() { $(selector).draghandler({ onDragEnter: function() { // $(this).doSomething(); }, onDragLeave: function() { // $(this).doSomethingElse(); } }); }); 

它完美地处理问题,而不会影响任何DOMfunction。

下载,其详细信息和解释在其Git存储库 。

我试图自己实现这一点,我不想要jquery或任何插件…我想处理file upload的方式,这是我认为最好的…所以这是我的解决scheme…它的工作原理! !…

文件结构

— / uploads {上传目录}

— /js/slyupload.js {javascript文件。}

— index.php

— upload.php

— styles.css {只是一点造型..}

现在让我们开始,这里的HTML代码…

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>my Dzone Upload...</title> <link rel="stylesheet" href="styles.css" /> </head> <body> <div id="uploads"></div> <div class="dropzone" id="dropzone">Drop files here to upload</div> <script type="text/javascript" src="js/slyupload.js"></script> </body> </html> 

然后这是附加的JavaScript文件::'js / slyupload.js'

 <!-- begin snippet: --> <!-- language: lang-js --> // JavaScript Document //ondragover, ondragleave... (function(){ var dropzone = document.getElementById('dropzone'); var intv; function displayUploads(data){ //console.log(data, data.length); var uploads = document.getElementById('uploads'),anchor,x; for(x=0; x < data.length; x++){ //alert(data[x].file); anchor = document.createElement('a'); anchor.href = data[x].file; anchor.innerText = data[x].name; uploads.appendChild(anchor); } } function upload(files){ //console.log(files); var formData = new FormData(), xhr = new XMLHttpRequest(), //for ajax calls... x; //for the loop.. for(x=0;x<files.length; x++){ formData.append('file[]', files[x]); /*//do this for every file... xhr = new XMLHttpRequest(); //open... and send individually.. xhr.open('post', 'upload.php'); xhr.send(formData);*/ } xhr.onload = function(){ var data = JSON.parse(this.responseText); //whatever comes from our php.. //console.log(data); displayUploads(data); //clear the interval when upload completes... clearInterval(intv); } xhr.onerror = function(){ console.log(xhr.status); } //use this to send all together.. and disable the xhr sending above... //open... and send individually.. intv = setInterval(updateProgress, 50); xhr.open('post', 'upload.php'); xhr.send(formData); //update progress... /* */ } function updateProgress(){ console.log('hello'); } dropzone.ondrop = function(e){ e.preventDefault(); //prevent the default behaviour.. of displaying images when dropped... this.className = 'dropzone'; //we can now call the uploading... upload(e.dataTransfer.files); //the event has a data transfer object... } dropzone.ondragover = function(){ //console.log('hello'); this.className = 'dropzone dragover'; return false; } dropzone.ondragleave = function(){ this.className = 'dropzone'; return false; } }(window)); 

现在的CSS,一点点样式…

 body{ font-family:Arial, Helvetica, sans-serif; font-size:12px; } .dropzone{ width:300px; height:300px; border:2px dashed #ccc; color:#ccc; line-height:300px; text-align:center; } .dropzone.dragover{ border-color:#000; color:#000; } 

我真的喜欢我在https://github.com/lolmaus/jquery.dragbetter/看到的,但是想分享一个可能的select。; 我的一般策略是在拖放它或它的任何一个孩子(通过冒泡)时,将一个背景样式应用到dropzone(而不是它的子项)。 然后,我在删除拖放区域时删除样式。 当移动到一个孩子的想法是,即使我离开它的时候删除从拖放的样式(dragleave火灾),我只是简单地重新应用风格的父母dropzone任何孩子。 问题当然是,从dropzone移动到dropzone的孩子时,dragicter会在dragleave之前被触发,所以我的样式被乱序应用。 对我来说,解决scheme是使用计时器强制dragenter事件回到消息队列,让我在dragleave之后处理它。 我使用闭包访问定时器callback事件。

 $('.dropzone').on('dragenter', function(event) { (function (event) { setTimeout(function () { $(event.target).closest('.dropzone').addClass('highlight'); }, 0); }) (event.originalEvent); }); 

这似乎工作在铬,即Firefox和工作,无论在dropzone的儿童数量。 对于保证重新sorting事件的超时,我有点不安,但是对于我的用例来说,它似乎工作得很好。

这个答案可以在这里find:

hover子元素时触发HTML5 dragleave

 var counter = 0; $('#drop').bind({ dragenter: function(ev) { ev.preventDefault(); // needed for IE counter++; $(this).addClass('red'); }, dragleave: function() { counter--; if (counter === 0) { $(this).removeClass('red'); } } }); 

我的版本:

 $(".dropzone").bind("dragover", function(e){ console.log('dragover'); }); $(".dropzone").bind("dragleave", function(e) { var stopDrag = false; if (!e.relatedTarget) stopDrag = true; else { var parentDrop = $(e.relatedTarget).parents('.dropzone'); if (e.relatedTarget != this && !parentDrop.length) stopDrag = true; } if (stopDrag) { console.log('dragleave'); } }); 

有了这个布局:

 <div class="dropzone"> <div class="inner-zone">Inner-zone</div> </div> 

我已经做了e.targete.currentTargete.relatedTargetdragoverdragleave事件元素类的转储。

它告诉我,离开父块( .dropzonee.relatedTarget不是这个块的孩子,所以我知道我脱离了dropzone。

使用greedy : true对孩子是greedy : true的作为一个functiondroppable 。 然后在第一层开始只点击事件。