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

Hri*_*sto 155 html jquery drag-and-drop file-upload dom-events

概观

我有以下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>
Run Code Online (Sandbox Code Playgroud)

问题

当我将文件拖过时<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. 我只安装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拖放的浏览器中,所以这个答案是不够的.

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

Ben*_*lph 163

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

.child-elements {
  pointer-events: none;
}
Run Code Online (Sandbox Code Playgroud)

  • 这种方法的唯一缺点是它会破坏子元素上的所有指针事件(例如,它们不再具有单独的`:hover`样式或单击事件处理程序).如果你想保留这些事件,这是我一直在使用的另一种解决方法:http://bensmithett.github.io/dragster/ (22认同)
  • 你已经在使用jQuery了,所以只需要`$('.element').addClass('dragging-over')`到你拖过的元素,`$('.element').removeClass('dragging -over')`当你拖出来的时候.然后在你的CSS中你可以有`.element.dragging-over*{pointer-events:none; }`.只有在拖动时才会从所有子元素中删除指针事件. (6认同)
  • 最佳方案!我甚至不认为我可以用这种优雅的方式使用css解决问题.非常感谢你! (4认同)
  • 结合 css 子选择器来访问 dropzone 的所有子项:`.dropzone * {pointer-events: none;}` (2认同)

Hri*_*sto 54

我终于找到了一个我很满意的解决方案.我实际上找到了几种方法来做我想要的但是没有一种方法像当前的解决方案一样成功...在一个解决方案中,我经常因为向#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;
});
Run Code Online (Sandbox Code Playgroud)

这很好用,但Firefox出现了问题,因为Firefox是双重调用的,dragenter所以我的计数器已关闭.但是,它不是一个非常优雅的解决方案.

然后我偶然发现了这个问题:如何在窗口外拖动时检测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;
    }
});
Run Code Online (Sandbox Code Playgroud)

这是干净和优雅的,似乎在我迄今为止测试的每个浏览器中都有效(尚未测试IE).

  • 看起来很好该死的:) (2认同)

hac*_*ack 36

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

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

}());
Run Code Online (Sandbox Code Playgroud)

  • 我最喜欢的答案就在这里.一点都不觉得难看:) (5认同)

bar*_*gar 27

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

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

在我的情况下,我在孩子们身上发生了很多事情,例如,将鼠标悬停在显示按钮以进行其他操作,内联编辑等等......但是,拖动过程中,这些都不是必需的,或者实际上甚至不需要.

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

  div.drag-target-parent-container.dragging-in-progress * {
    pointer-events: none;
  }
Run Code Online (Sandbox Code Playgroud)

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


Ble*_*der 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');
    });

});?
Run Code Online (Sandbox Code Playgroud)

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>
?
Run Code Online (Sandbox Code Playgroud)

  • 设置dropzone覆盖以隐藏/显示按需:http://jsfiddle.net/yYF3S/13/ (3认同)

Oli*_*ver 5

问题是,dropzone中的元素当然是dropzone的一部分,当你输入子节点时,你就离开了父节点.解决这个问题并不容易.您可以尝试向子项添加事件,并将您的类再次添加到父项.

$("#dropzone,#dropzone *").on('dragenter', function(event) {

    // add a class to #dropzone

    event.stopPropagation(); // might not be necessary
    event.preventDefault();
    return false;
});
Run Code Online (Sandbox Code Playgroud)

您的活动仍会多次开火,但没有人会看到.

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

$("#dropzone,#dropzone *").on('dragenter dragover', function(event) {

    // add a class to #dropzone

    event.stopPropagation(); // might not be necessary
    event.preventDefault();
    return false;
});
Run Code Online (Sandbox Code Playgroud)

仅为dropzone定义dragleave事件.

  • 但这不会解决问题.真正的问题是`dragleave` ...当我离开一个孩子时,我可能仍然在父母`#dropzone`中,但这不会再次触发'dragenter`事件:( (2认同)

Vah*_*ian 5

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

#dropzone.dragover *{
   pointer-events: none;
}
Run Code Online (Sandbox Code Playgroud)

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

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

$("#dropzone").on("dragleave", function (event) {
   $("#dropzone").removeClass("dragover");
});
Run Code Online (Sandbox Code Playgroud)

  • 对我来说,这会导致叠加层不断闪烁。 (4认同)