我遇到的问题是,一个元素的dragleave事件是在悬停该元素的子元素时触发的。同样,当再次悬停回父元素时,dragenter不会被触发。

我做了一个简化的小提琴:http://jsfiddle.net/pimvdb/HU6Mk/1/。

HTML:

<div id="drag" draggable="true">drag me</div>

<hr>

<div id="drop">
    drop here
    <p>child</p>
    parent
</div>

使用以下JavaScript:

$('#drop').bind({
                 dragenter: function() {
                     $(this).addClass('red');
                 },

                 dragleave: function() {
                     $(this).removeClass('red');
                 }
                });

$('#drag').bind({
                 dragstart: function(e) {
                     e.allowedEffect = "copy";
                     e.setData("text/plain", "test");
                 }
                });

它应该做的是,当拖拽一些东西时,通过将drop div设置为红色来通知用户。这是可行的,但是如果你拖动到p子元素中,拖动键就会触发,div就不再是红色了。移动回下拉div也不会使它再次变红。有必要完全移出拖放div,并再次拖回它,使其变为红色。

是否有可能阻止dragleave在拖动到子元素时发射?

2017年更新:TL;DR,查找CSS指针事件:无;如@ h.d.所述。在现代浏览器和IE11中都能运行。


当前回答

我能够做到这一点使用超时dragleave。与其他使用这种方法的答案不同,我认为在拖拽时重置这个超时以避免闪烁是至关重要的。

我写的只是你需要的函数,然后使用你选择的框架进行绑定

let dragoverTimeout;
const onDragOver = (e: Event) => {
  e.preventDefault();
  e.stopPropagation();

  if (dragoverTimeout) {
    window.clearTimeout(dragoverTimeout);
    dragoverTimeout = null;
  }
  // Add your class here
}

const onDragLeave = (e: Event) => {
  e.preventDefault();
  e.stopPropagation();

  if (!dragoverTimeout) {
    dragoverTimeout = window.setTimeout(() => {
      // Remove your class here
    }, 100);
  }
}

const onDrop = (e) => {
  e.preventDefault();
  e.stopPropagation();

  const files = e.dataTransfer.files;
  // Remove your class here

  if (files.length > 0) {
    this.uploadFile(files);
  }
}

其他回答

接受的答案在某些情况下可能有效,但如果您有多个重叠的子元素,它将很快崩溃。

根据您的用例,本文的顶部答案可能更简单、更清晰

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

下面是Chrome的解决方案:

.bind('dragleave', function(event) {
                    var rect = this.getBoundingClientRect();
                    var getXY = function getCursorPosition(event) {
                        var x, y;

                        if (typeof event.clientX === 'undefined') {
                            // try touch screen
                            x = event.pageX + document.documentElement.scrollLeft;
                            y = event.pageY + document.documentElement.scrollTop;
                        } else {
                            x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
                            y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
                        }

                        return { x: x, y : y };
                    };

                    var e = getXY(event.originalEvent);

                    // Check the mouseEvent coordinates are outside of the rectangle
                    if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) {
                        console.log('Drag is really out of area!');
                    }
                })

不确定这是否跨浏览器,但我在Chrome中测试,它解决了我的问题:

我想拖放一个文件在整个页面,但我的dragleave被解雇时,我拖过子元素。我的解决方法是查看鼠标的x和y:

我有一个div覆盖我的整个页面,当页面加载我隐藏它。

当你拖拽文件时,我显示它,当你拖拽到父文件时,它处理它,当你离开父文件时,我检查x和y。

$('#draganddrop-wrapper').hide();

$(document).bind('dragenter', function(event) {
    $('#draganddrop-wrapper').fadeIn(500);
    return false;
});

$("#draganddrop-wrapper").bind('dragover', function(event) {
    return false;
}).bind('dragleave', function(event) {
    if( window.event.pageX == 0 || window.event.pageY == 0 ) {
        $(this).fadeOut(500);
        return false;
    }
}).bind('drop', function(event) {
    handleDrop(event);

    $(this).fadeOut(500);
    return false;
});

下面是另一个使用document.elementFromPoint的解决方案:

 dragleave: function(event) {
   var event = event.originalEvent || event;
   var newElement = document.elementFromPoint(event.pageX, event.pageY);
   if (!this.contains(newElement)) {
     $(this).removeClass('red');
   }
}

希望这有用,这是小提琴。

在这个问题被提出之后已经有一段时间了,并且提供了很多解决方案(包括丑陋的黑客)。

我设法解决了我最近遇到的同样的问题,感谢这个答案中的答案,并认为它可能对来到这个页面的人有帮助。 整个思想就是存储事件。每当对任何父元素或子元素调用时,ondrageenter中的目标。然后在ondragleave检查当前目标(event.target)是否等于你在ondragenter中存储的对象。

这两者匹配的唯一情况是当您的拖动离开浏览器窗口时。

这样工作正常的原因是当鼠标离开一个元素(例如el1)并进入另一个元素(例如el2)时,首先是el2。调用Ondragenter,然后调用el1.ondragleave。仅当拖动离开/进入浏览器窗口时,事件。目标将是“在两个el2。Ondragenter和el1.ondragleave。

这是我的工作样本。我已经在IE9+、Chrome、Firefox和Safari上进行了测试。

(function() {
    var bodyEl = document.body;
    var flupDiv = document.getElementById('file-drop-area');

    flupDiv.onclick = function(event){
        console.log('HEy! some one clicked me!');
    };

    var enterTarget = null;

    document.ondragenter = function(event) {
        console.log('on drag enter: ' + event.target.id);
        enterTarget = event.target;
        event.stopPropagation();
        event.preventDefault();
        flupDiv.className = 'flup-drag-on-top';
        return false;
    };

    document.ondragleave = function(event) {
        console.log('on drag leave: currentTarget: ' + event.target.id + ', old target: ' + enterTarget.id);
        //Only if the two target are equal it means the drag has left the window
        if (enterTarget == event.target){
            event.stopPropagation();
            event.preventDefault();
            flupDiv.className = 'flup-no-drag';         
        }
    };
    document.ondrop = function(event) {
        console.log('on drop: ' + event.target.id);
        event.stopPropagation();
        event.preventDefault();
        flupDiv.className = 'flup-no-drag';
        return false;
    };
})();

这是一个简单的html页面:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Multiple File Uploader</title>
<link rel="stylesheet" href="my.css" />
</head>
<body id="bodyDiv">
    <div id="cntnr" class="flup-container">
        <div id="file-drop-area" class="flup-no-drag">blah blah</div>
    </div>
    <script src="my.js"></script>
</body>
</html>

使用适当的样式,我所做的是使内部div (#file-drop area)在文件被拖到屏幕时变得更大,以便用户可以轻松地将文件放到适当的位置。