在拖动子元素时触发父元素的“dragleave”
概观
我有以下HTML结构,并将dragenter
和dragleave
事件附加到<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
指令,即不酷。
这种行为有两个原因是有问题的:
-
我只是将
dragenter
和dragleave
到<div id="dropzone">
所以我不明白为什么儿童元素也附加了这些事件。 -
我仍在拖动
<div id="dropzone">
元素,同时将dragleave
hover在它的子元素上,所以我不想让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
孩子的元素。 希望这可以帮助 :)
*当我dragleave
或dragleave
时,添加'.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.target
, e.currentTarget
, e.relatedTarget
的dragover
和dragleave
事件元素类的转储。
它告诉我,离开父块( .dropzone
) e.relatedTarget
不是这个块的孩子,所以我知道我脱离了dropzone。
使用greedy : true
对孩子是greedy : true
的作为一个functiondroppable
。 然后在第一层开始只点击事件。