185

概述

我具有以下 HTML 结构,并且已将dragenteranddragleave事件附加到<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&附加dragleave到了,<div id="dropzone">所以我不明白为什么子元素也附加了这些事件。

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

jsFiddle

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

问题

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

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

谷歌和 Dropbox 似乎已经解决了这个问题,但是他们的源代码被缩小/复杂,所以我无法从他们的实现中解决这个问题。

4

22 回答 22

184

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

.child-elements {
  pointer-events: none;
}
于 2012-12-25T05:35:46.817 回答
62

我终于找到了一个我满意的解决方案。实际上,我找到了几种方法来做我想做的事,但没有一个像当前的解决方案那样成功......在一个解决方案中,由于向#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

所以我接受了答案并将其应用于我的情况:

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

于 2012-06-05T23:02:20.883 回答
42

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

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

}());
于 2012-09-04T20:11:56.897 回答
37

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

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

在我的例子中,我在孩子身上发生了很多事情,例如,悬停以显示用于其他操作的按钮、内联编辑等......但是,拖动过程中,这些都不是必需的,甚至实际上是不需要的。

在我的例子中,我使用这样的东西来选择性地关闭父容器的所有子节点的指针事件:

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

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

于 2014-12-03T18:21:55.960 回答
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>
​
于 2012-06-03T03:14:41.810 回答
5

问题是,放置区内的元素当然是放置区的一部分,当您进入子级时,您会离开父级。解决这个问题并不容易。您可以尝试向孩子添加事件,也可以将您的班级再次添加到父母。

$("#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 事件。

于 2012-06-03T03:00:38.327 回答
4

正如本答案提到的那样,您可以防止子节点触发事件,但如果您需要绑定一些事件,请执行以下操作:

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

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

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

$("#dropzone").on("dragleave", function (event) {
   $("#dropzone").removeClass("dragover");
});
于 2013-08-24T14:18:30.457 回答
3

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

真是太棒了。

为处理真正的拖动离开功能而创建的特殊事件。

HTML5 dragleave 事件更像 mouseout。创建此插件是为了在拖动时复制 mouseleave 样式功能。

使用示例:

$('#myelement').on('dragout',function(event){ // 你的代码 });

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

于 2013-05-23T18:56:37.800 回答
3

我的两分钱:在 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;
});
于 2016-01-29T14:21:13.043 回答
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
});
于 2013-07-03T15:15:46.643 回答
2

我的版本:

$(".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不是该块的子块,所以我知道我已经离开了放置区。

于 2017-10-16T11:46:31.030 回答
2

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

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

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

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

于 2019-09-25T06:40:34.850 回答
1

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

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

$(document).ready(function() {

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

});

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

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

于 2015-04-25T18:45:11.670 回答
1

所以对我来说,这种方法pointer-events: none;效果不太好......所以这是我的替代解决方案:

    #dropzone {
        position: relative;
    }

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

这样,dragleave父元素(在子元素上)或dragover子元素是不可能的。希望这可以帮助 :)

*当我dragenterdragleave. 但是,如果您在没有它的情况下工作,请离开课程。

于 2014-03-16T23:03:13.457 回答
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 闪烁错误,或者至少修复了导致我出现问题的它的排列。

于 2016-08-06T12:38:43.257 回答
1

我实际上很喜欢我在https://github.com/lolmaus/jquery.dragbetter/看到的内容但想分享一个可能的选择。我的一般策略是在拖放区域或其任何子项(通过冒泡)时将背景样式应用于拖放区(而不是其子项)。然后我在拖动放置区域时删除样式。想法是当移动到一个孩子时,即使我在离开它时从 dropzone 中删除了样式(dragleave 触发),我只需在 dragentering 任何孩子时将样式重新应用到父 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中有多少孩子都可以使用。我对保证事件重新排序的超时有点不安,但它似乎对我的用例很有效。

于 2016-11-10T17:44:27.863 回答
1

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

悬停子元素时触发 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');
        }
    }
});
于 2017-02-05T02:09:10.927 回答
0

我有一个类似的问题,我用这种方式解决了它:

问题:当用户在“放置区”(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));} 
} 
于 2013-05-22T11:24:47.533 回答
0

在这里,最简单的解决方案之一●︿●</p>

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

你可以这样做:

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

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

于 2013-05-05T17:39:18.233 回答
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;
}

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

于 2014-04-07T05:56:07.853 回答
-1

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

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

文件结构:

--- /uploads {上传目录}

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

--- index.php

--- 上传.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;
}

于 2015-07-23T03:41:28.527 回答
-2

greedy : true给孩子用作 的函数droppable。然后仅启动在第一层上单击的事件。

于 2016-07-12T19:15:01.850 回答