拖动子元素时,父元素的“ 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事件,然后为 <div id="dropzone">元素触发 dragleave事件。

如果我再次将鼠标悬停在 <div id="dropzone">元素上,dragenter事件将再次被激发,这很酷,但是对于刚刚离开的子元素,dragleave事件将被激发,因此执行 removeClass指令,这并不酷。

这种行为是有问题的,原因有二:

  1. 我只是附加 dragenterdragleave<div id="dropzone">,所以我不明白为什么子元素有这些事件附加以及。

  2. 我仍然拖动在 <div id="dropzone">元素,同时悬停在其子级,所以我不希望 dragleave发射!

JsFiddle

这里有一个可以修补的 jsFiddle: http://jsfiddle.net/yYF3S/2/

提问

所以... 我怎样才能使它成为这样,当我拖动一个文件在 <div id="dropzone">元素上,即使我拖动任何子元素,dragleave也不会触发... 它应该只有在我离开 <div id="dropzone">元素时才会触发... 悬停/拖动在元素 不应该的边界内的任何地方触发 dragleave事件。

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

看起来 Google 和 Dropbox 已经解决了这个问题,但是他们的源代码是缩小/复杂的,所以我还没能从他们的实现中解决这个问题。

72374 次浏览

问题是,你在降落区内的元素当然是降落区的一部分,当你进入孩子时,你离开了父母。解决这个问题并不容易。您可以尝试将事件添加到子类中,也可以将类再次添加到父类中。

$("#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;
});

只为下拉区定义 Dragleave 事件。

这似乎是一个 Chrome bug。

我能想到的唯一解决办法是创建一个透明的覆盖元素来捕捉事件: 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>
​

我终于找到了我满意的解决方案。我实际上找到了几种方法来做我想做的事情,但是没有一种方法像当前的解决方案那样成功... ... 在一个解决方案中,我经历了频繁的闪烁,这是为 #dropzone元素添加/删除边框的结果... ... 在另一个解决方案中,如果你将鼠标悬停在远离浏览器的地方,边框就永远不会被删除。

不管怎样,我最好的解决办法是:

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,所以我的计数器关闭了。尽管如此,这并不是一个非常优雅的解决方案。

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

所以我把 答案应用到我的情况:

$.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;
}
});

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

这是一个有点丑陋,但它工程该死! ..。

在您的“ dragenter”处理程序中存储 event.target (在您的闭包或其他内容中的变量中) ,然后在您的“ 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");
}
});


}());

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

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

这里,最简单的解决方案之一●〈●

看看这个 小提琴 < ——尝试在框中拖动一些文件

你可以这样做:

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);

你应该已经知道这里发生了什么。
看看这把小提琴。

我也遇到过类似的问题,我用这种方法解决了:

问题是: 当用户在“ drop zone”(ul 元素)中删除一个元素时,函数 drop (ev)会被触发,但不幸的是,当元素在其子元素(li 元素)中删除时,也会触发函数 drop (ev)。

解决办法:

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));}
}

如果你正在使用 jQuery,看看这个: Https://github.com/dancork/jquery.event.dragout

真是太棒了。

创建特殊事件来处理真正的 Dragleave 功能。

HTML5 dragleave 事件的工作方式更像 mouseout 创建,以复制鼠标离开样式的功能,同时 拖着。

用法例子:

$(’# myelement’) . on (‘ draout’,function (event){ //你的密码 });

编辑: 实际上,我不认为它依赖于 jQuery,即使没有它,你也可以直接使用代码。

@ hristo 我有一个更优雅的解决方案,如果你需要的话,可以试试。

你的努力没有白费。我一开始试着用了你的,但是在 FF,Chrome 里有不同的问题。在花了这么多个小时之后,我得到了那个建议,并且完全按照预期的那样工作。

实现是这样的,我还利用了视觉提示来正确地引导用户关于放置区域的信息。

$(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
});

正如 这个答案中提到的 Benr,您可以防止子节点触发事件,但是如果您需要绑定某些事件,请执行以下操作:

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

然后把这个加到你的 JS 代码中:

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


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

所以对于我来说,pointer-events: none;的方法并没有起到很好的作用... ... 所以这里是我的替代解决方案:

    #dropzone {
position: relative;
}


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

这样就不可能使 dragleave成为(子元素上的)父元素或者 dragover成为子元素。希望这能有所帮助:)

我在 dragenter或者 dragleave中添加的’. active’-class。但是如果你没有使用这个类,那么就把这个类留下。

我试图实现这个自己的文件上传框,其中的框颜色会改变当用户拖动文件到空间。

我找到了一个结合了 Javascript 和 CSS 的解决方案。假设您有一个带有 id 为 #dropdiv可丢弃区域。把这个添加到你的 Javascript 中:

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


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

然后,将这个添加到您的 CSS 中,以使所有的子类 .inactive失效:

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

因此,只要用户在框上拖动元素,子元素就会处于非活动状态。

起初,我同意人们放弃 pointer-events: none方法,但后来我问自己:

您真的需要指针事件来处理子元素 而拖曳正在进行中吗?

在我的例子中,我有很多东西在孩子中进行,例如悬停显示附加操作的按钮,内联编辑等等。.然而,没有的那个是必要的,甚至事实上想要 期间的一个拖动。

在我的例子中,我使用类似的方法有选择地关闭父容器的所有子节点的指针事件:

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

使用您喜欢的方法在 dragEnter/dragLeave事件处理程序中添加/删除类 dragging-in-progress,就像我在 dragStart等中所做的那样。Al.

我对这里提出的任何变通方法都不满意,因为我不想失去对子元素的控制。

因此,我使用了一种不同的逻辑方法,将其转换为一个名为 Jquery-draghandler的 jQuery 插件。它绝对不会操纵 DOM,从而保证高性能。它的用法很简单:

$(document).ready(function() {


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


});

它在不损害任何 DOM 功能的情况下完美地处理问题。

下载,详细资料及解释其 Git 仓库

我试图自己实现它,而且我也不想要 Jquery 或任何插件。

我想处理文件上传,以下方式被认为是最适合我的:

文件结构:

——-/uploads { uploads 目录}

——/js/slyupload.js { javascript file. }

—— index.php

上传

——-styles.css { just a little styling. . }

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://jsfiddle.net/t6q4shat/

超文本标示语言

<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;
});

使用 greedy : true作为子层的 droppable函数。然后只在第一层单击事件。

超级简单的快速修复,没有广泛测试,但现在可以在 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 闪烁的 bug,或者至少是它的排列造成了我的问题。

实际上我很喜欢我在 https://github.com/lolmaus/jquery.dragbetter/看到的东西,但是我想和大家分享一个可能的选择。 我的总体策略是,当拖动进入该拖动区域或其任何子区域(通过冒泡)时,将背景样式应用于该拖动区域(而不是其子区域)。然后我删除样式时拖动离开下降区域。我的想法是,当移动到一个子元素时,即使我在离开拖放区域时移除了该样式(Dragleave 触发) ,我也会在拖入任何子元素时将该样式重新应用到父元素的拖放区域。当然,问题在于当从下拉区移动到下拉区的子区时,拖动区域会在拖动区域之前向子区域开火,所以我的风格被应用到了无序的地方。对我来说,解决方案是使用一个计时器来强制 Dragenter 事件返回到消息队列,允许我在 Dragleave 之后处理它。我使用了一个闭包来访问计时器回调上的事件。

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

这似乎工作在铬,即,火狐和工作,无论在下拉区的孩子的数量。对于确保事件重新排序的超时,我略感不安,但它似乎对我的用例非常有效。

这个问题的答案可以在这里找到:

当悬停子元素时触发 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事件转储了元素类。

它告诉我在离开父块(.dropzone)时,e.relatedTarget不是这个块的子块,所以我知道我已经离开了下拉区。

对不起,这是 javascript 而不是 jquery,但对我来说,这是解决这个问题最合乎逻辑的方法。 浏览器应该在 dropenter 之前调用 dropleave (前一个元素)(对于新元素,因为在离开第一个元素之前有些东西不能输入其他东西,我不明白他们为什么要这么做! 所以你只需要像这样拖延一下:

function mydropleave(e)
{
e.preventDefault();
e.stopPropagation();


setTimeout(function(e){ //the things you want to do },1);
}

下坠后就会发生下坠,仅此而已!