jQuery 拖动子元素时父元素的“dragleave”触发

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10867506/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 09:52:38  来源:igfitidea点击:

'dragleave' of parent element fires when dragging over children elements

jqueryhtmlfile-uploaddrag-and-dropdom-events

提问by Hristo

Overview

概述

I have the following HTML structure and I've attached the dragenterand dragleaveevents to the <div id="dropzone">element.

我有以下 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>

Problem

问题

When I drag a file over the <div id="dropzone">, the dragenterevent is fired as expected. However, when I move my mouse over a child element, such as <div id="drag-n-drop">, the dragenterevent is fired for the <div id="drag-n-drop">element and then the dragleaveevent is fired for the <div id="dropzone">element.

当我将文件拖到 上时<div id="dropzone">dragenter会按预期触发该事件。但是,当我将鼠标移到子元素上时,例如<div id="drag-n-drop">,会dragenter为该<div id="drag-n-drop">元素dragleave触发事件,然后为该<div id="dropzone">元素触发该事件。

If I hover over the <div id="dropzone">element again, the dragenterevent is again fired, which is cool, but then the dragleaveevent is fired for the child element just left, so the removeClassinstruction is executed, which is not cool.

如果我<div id="dropzone">再次将鼠标悬停在该元素上,dragenter则再次触发该事件,这很酷,但是随后dragleave为刚刚离开的子元素触发了该事件,因此removeClass执行了指令,这很酷。

This behavior is problematic for 2 reasons:

这种行为有问题有两个原因:

  1. I'm only attaching dragenter& dragleaveto the <div id="dropzone">so I don't understand why the children elements have these events attached as well.

  2. I'm still dragging over the <div id="dropzone">element while hovering over its children so I don't want dragleaveto fire!

  1. 我只是将dragenter&附加dragleave到,<div id="dropzone">所以我不明白为什么子元素也附加了这些事件。

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

jsFiddle

js小提琴

Here's a jsFiddle to tinker with: http://jsfiddle.net/yYF3S/2/

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

Question

So... how can I make it such that when I'm dragging a file over the <div id="dropzone">element, dragleavedoesn't fire even if I'm dragging over any children elements... it should only fire when I leave the <div id="dropzone">element... hovering/dragging around anywhere within the boundaries of the element should nottrigger the dragleaveevent.

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

I need this to be cross-browser compatible, at least in the browsers that support HTML5 drag-n-drop, so this answeris not adequate.

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

It seems like Google and Dropbox have figured this out, but their source code is minified/complex so I haven't been able to figure this out from their implementation.

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

采纳答案by Hristo

I finally found a solution I'm happy with. I actually found several ways to do what I want but none were as successful as the current solution... in one solution, I experienced frequent flickering as a result of adding/removing a border to the #dropzoneelement... in another, the border was never removed if you hover away from the browser.

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

Anyway, my best hacky solution is this:

无论如何,我最好的解决方案是这样的:

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

This works pretty well but issues came up in Firefox because Firefox was double-invoking dragenterso my counter was off. But nevertheless, its not a very elegant solution.

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

Then I stumbled upon this question: How to detect the dragleave event in Firefox when dragging outside the window

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

So I took the answerand applied it to my situation:

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

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

This is clean and elegant and seems to be working in every browser I've tested so far (haven't tested IE yet).

这是干净和优雅的,似乎在我迄今为止测试过的每个浏览器中都可以使用(还没有测试过 IE)。

回答by Ben Rudolph

If you don't need to bind events to the child elements, you can always use the pointer-events property.

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

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

回答by hacklikecrack

This is a little ugly but it works dammit!...

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

On your 'dragenter' handler store the event.target (in a variable inside your closure, or whatever), then in your 'dragleave' handler only fire your code if event.target === the one you stored.

在您的 'dragenter' 处理程序上存储 event.target (在您的闭包内的变量中,或其他任何地方),然后在您的 'dragleave' 处理程序中,如果 event.target === 您存储的那个,则仅触发您的代码。

If your 'dragenter' is firing when you don't want it to (i.e. when it's entering after leaving child elements), then the last time it fires before the mouse leaves the parent, it's on the parent, so the parent will always be the final 'dragenter' before the intended 'dragleave'.

如果您的“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");
        }
    });

}());

回答by bargar

At first, I agreed with folks discarding the pointer-events: noneapproach. But then I asked myself:

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

Do you really need pointer-events to work on the child elements while dragging is in progress?

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

In my case, I have lots of stuff going on in the children, e.g. hover to show buttons for additional actions, inline-editing, etc... However, noneof that is necessary or in fact even desired duringa drag.

就我而言,我在子项中进行了很多操作,例如悬停以显示用于附加操作的按钮、内联编辑等……但是,拖动过程中这些都不是必需的,甚至实际上是不需要的。

In my case, I use something like this to turn pointer events off selectively for all child nodes of the parent container:

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

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

Use your favorite approach to add/remove the class dragging-in-progressin the dragEnter/dragLeaveevent handlers, as I did or do the same in dragStart, et. al.

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

回答by Blender

This seems to be a Chrome bug.

这似乎是一个 Chrome 错误。

The only workaround that I could think of was to create a transparent overlay element to capture your events: http://jsfiddle.net/yYF3S/10/

我能想到的唯一解决方法是创建一个透明的覆盖元素来捕获您的事件:http: //jsfiddle.net/yYF3S/10/

JS:

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:

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>
?

回答by Oliver

The problem is, that your elements inside the dropzones are of course part of the dropzone and when you enter the children, you leave the parent. Solving this is not easy. You might try adding events to the children too adding your class again to the parent.

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

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

    // add a class to #dropzone

    event.stopPropagation(); // might not be necessary
    event.preventDefault();
    return false;
});

Your events will still fire multiple times but nobody will see.

您的事件仍会多次触发,但没有人会看到。

//Edit: Use the dragmove event to permanently overwrite the dragleave event:

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

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

    // add a class to #dropzone

    event.stopPropagation(); // might not be necessary
    event.preventDefault();
    return false;
});

Define the dragleave event only for the dropzone.

仅为放置区定义 dragleave 事件。

回答by Vahid Ashrafian

As benrmentioned in this answer, you can prevent child nodes to fire on events, but if you need to bind some events, do this:

正如benr中提到的这个答案,就可以防止子节点火事件,但如果你需要绑定一些事件,这样做:

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

And add this one to your JS code:

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

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

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

回答by nkkollaw

If you're using jQuery, check this out: https://github.com/dancork/jquery.event.dragout

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

It's truly awesome.

真是太棒了。

Special event created to handle true dragleave functionality.

HTML5 dragleave event works more like mouseout. This plugin was created to replicate the mouseleave style functionality whilst dragging.

Usage Example:

$('#myelement').on('dragout',function(event){ // YOUR CODE });

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

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

用法示例:

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

EDIT: actually, I don't think it's dependent on jQuery, you can probably just use the code even without it.

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

回答by visitsb

@hristo I have a much more elegant solution. Check that if that is something you could use.

@hristo 我有一个更优雅的解决方案。检查这是否是您可以使用的东西。

Your effort was not wasted after all. I managed to use yours at first, but had different problems in FF, Chrome. After spending so many hours I got that suggestion working exactly as intended.

毕竟你的努力没有白费。一开始我设法使用了你的,但在 FF、Chrome 中遇到了不同的问题。花了这么多小时后,我得到了完全按预期工作的建议。

Here's how the implementation is. I also took advantage of visual cues to correctly guide users about the drop zone.

这是实现的方式。我还利用视觉提示正确引导用户了解放置区。

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

回答by jeremie.b

My two cents: Hide a layer over your dropzone then show it when you dragenter, and target the dragleave on it.

我的两分钱:在你的 dropzone 上隐藏一个图层,然后在你 dragenter 时显示它,并在它上面瞄准 dragleave。

Demo : https://jsfiddle.net/t6q4shat/

演示:https: //jsfiddle.net/t6q4shat/

HTML

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

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

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