拖动子元素时,父元素的'dragleave'会触发

时间:2012-06-03 02:43:15

标签: jquery html5 javascript-events file-upload drag-and-drop

概述

我有以下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">)上时,会为dragenter元素触发<div id="drag-n-drop">事件,然后触发dragleave事件<div id="dropzone">元素。

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

出于以下原因,此行为存在问题:

  1. 我只附上dragenter&amp; dragleave<div id="dropzone">所以我不明白为什么子元素也会附加这些事件。

  2. 我仍然拖着<div id="dropzone">元素,同时将鼠标悬停在其子节点上,所以我不希望dragleave开火!

  3. 的jsfiddle

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

    问题

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

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

    Google和Dropbox似乎已经明白这一点,但是他们的源代码已经缩小/复杂,所以我无法从他们的实现中看出来。

22 个答案:

答案 0 :(得分:155)

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

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

答案 1 :(得分:52)

我终于找到了一个我很满意的解决方案。我实际上找到了几种方法来做我想要的但是没有一种方法能像当前的解决方案一样成功......在一个解决方案中,我经常因为向#dropzone元素添加/删除边框而频繁闪烁...另一方面,如果您将鼠标悬停在浏览器之外,则永远不会删除边框。

无论如何,我最好的hacky解决方案就是:

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所以我的计数器已关闭。但是,它不是一个非常优雅的解决方案。

然后我偶然发现了这个问题:How to detect the dragleave event in Firefox when dragging outside the window

所以我带了the answer并将其应用到我的情况中:

$.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)。

答案 2 :(得分:35)

这有点难看,但它确实有害!...

在你的'dragenter'处理程序中存储event.target(在你的闭包中的变量中,或者其他),然后在你的'dragleave'处理程序中,只有当event.target ===你存储的那个时才激活你的代码。 / p>

如果你的'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");
        }
    });

}());

答案 3 :(得分:24)

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

  

在拖动进行过程中,你真的需要指针事件来处理子元素 吗?

在我的情况下,我在孩子们身上发生了很多事情,例如:悬停以显示其他操作,内联编辑等按钮...但是拖动时

就我而言,我使用类似的东西来有选择地关闭父容器的所有子节点的指针事件:

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

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

答案 4 :(得分:12)

这似乎是Chrome漏洞。

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

答案 5 :(得分:5)

问题是,dropzone中的元素当然是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事件。

答案 6 :(得分:3)

如果您正在使用jQuery,请查看以下内容: https://github.com/dancork/jquery.event.dragout

真的很棒。

  

为处理真正的dragleave功能而创建的特殊事件。

     

HTML5 dragleave事件更像mouseout。这个插件是   创建时可以复制mouseleave样式功能   拖动。

     

用法示例:

     

$( '#myelement')。在( '曳出',函数(事件){       //你的代码   });

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

答案 7 :(得分:3)

benr中提到的this answer,您可以阻止子节点触发事件,但如果您需要绑定某些事件,请执行以下操作:

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

将这个添加到您的JS代码中:

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

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

答案 8 :(得分:2)

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

答案 9 :(得分:2)

我的两分钱: 在您的dropzone上隐藏一个图层,然后在您的dragenter时显示它,并在其上定位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;
});

答案 10 :(得分:1)

我的版本:

$(".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.currentTarget事件的e.relatedTargetdragoverdragleave转储了元素类。

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

答案 11 :(得分:1)

所以对我而言,方法pointer-events: none;并没有太好用......所以这是我的另类解决方案:

    #dropzone {
        position: relative;
    }

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

这样就不可能dragleave父项(在孩子身上)或dragover子元素。希望这会有所帮助:)

*我在dragenterdragleave时添加的&。39. .active&#39;类。但是,如果你没有那个工作就离开课堂。

答案 12 :(得分:1)

超级简单快速解决此问题,未经过广泛测试,但现在可在Chrome中使用。

原谅Coffeescript。

  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闪烁错误,或者至少是导致我出现问题的排列错误。

答案 13 :(得分:0)

对不起,它不是JavaScript的javascript,但对我来说,这是解决该问题的最合乎逻辑的方法。 浏览器应在dropenter(新元素之前)之前调用(上一个元素的)dropleave,因为在离开最重要的东西之前某些东西无法输入,我不明白他们为什么这么做! 因此,您只需要像这样延迟de dropleave:

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

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

dropenter将在dropleave之后发生,仅此而已!

答案 14 :(得分:0)

这个答案可以在这里找到:

Is there a better way to do this command to find strings in a file excluding special characters?

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

答案 15 :(得分:0)

我实际上喜欢我在https://github.com/lolmaus/jquery.dragbetter/看到的内容,但想分享一个可能的选择。 我的一般策略是在放置它或它的任何一个孩子时(通过冒泡)将一个背景样式应用于dropzone(而不是它的孩子)。然后我在拖放放置区时删除样式。想法是搬到一个孩子的时候,即使我离开它时从掉落区移除了这个样式(dragleave fires),我只是简单地将这个样式重新应用到父级dropzone上,以便放弃任何孩子。问题当然是当从dropzone移动到dropzone的孩子时,dragenter会在dragleave之前对孩子开火,所以我的样式被乱解了。我的解决方案是使用计时器强制将dragenter事件返回到消息队列,允许我在dragleave之后处理它。我使用了一个闭包来访问定时器回调中的事件。

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

这似乎适用于chrome,即firefox,无论dropzone中的子项数量多少都能正常工作。我对保证重新排序事件的超时有点不安,但它似乎对我的用例非常有效。

答案 16 :(得分:0)

我试图自己实现这个,我也不想要Jquery或任何插件。

我想按照最适合我的方式处理文件上传:

文件结构:

--- / uploads {uploads directory}

--- /js/slyupload.js {javascript file。}

--- 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文件::&#39; js / slyupload.js&#39;

<!-- 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:

&#13;
&#13;
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;
}
&#13;
&#13;
&#13;

答案 17 :(得分:0)

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

所以我使用了不同的逻辑方法,将其转换为jQuery插件,称为jquery-draghandler。它绝对不会操纵DOM,保证高性能。它的用法很简单:

$(document).ready(function() {

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

});

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

Git repository上下载,详细说明和解释。

答案 18 :(得分:0)

我正在尝试自己实现一个文件上传框,当用户将文件拖入空间时,框颜色会发生变化。

我找到了一个很好的Javascript和CSS混合解决方案。假设您有一个带有div且ID为#drop的可放置区域。将其添加到您的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;
}

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

答案 19 :(得分:0)

我遇到了类似的问题,我这样解决了问题:

问题: 当用户在“drop zone”(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));} 
} 

答案 20 :(得分:0)

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

看看这个fiddle&lt; - 尝试拖动框内的一些文件

您可以这样做:

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

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

答案 21 :(得分:-1)

greedy : true作为droppable的函数用于子项。然后开始只在第一层点击事件。