我遇到的问题是,一个元素的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在拖动到子元素时发射?

Yes.

#drop * {pointer-events: none;}

CSS似乎足够Chrome。

当使用它与Firefox, #drop不应该有文本节点直接(否则有一个奇怪的问题,一个元素“离开它自己”),所以我建议只留下一个元素(例如,使用一个div在#drop里面放所有的东西)

下面是一个jsfiddle解决原始问题(损坏的)示例。

我还从@Theodore Brown的例子中做了一个简化版本,但只基于这个CSS。

不过,并不是所有浏览器都实现了这种CSS: http://caniuse.com/pointer-events

看到Facebook的源代码,我可以找到这个指针事件:无;但是,它可能多次与优雅的降级回退一起使用。至少它是如此简单,解决了许多环境中的问题。

其他回答

如果你使用的是HTML5,你可以得到父类的clientRect:

let rect = document.getElementById("drag").getBoundingClientRect();

然后在parent.dragleave()中:

dragleave(e) {
    if(e.clientY < rect.top || e.clientY >= rect.bottom || e.clientX < rect.left || e.clientX >= rect.right) {
        //real leave
    }
}

这是一个jsfiddle

我也有同样的问题,并试图使用pk7s的解决方案。它工作,但它可以做得更好一点,没有任何额外的dom元素。

基本上想法是一样的-在可掉落的区域上添加一个额外的不可见的覆盖。让我们在没有任何额外dom元素的情况下这样做。下面是CSS伪元素开始发挥作用的部分。

Javascript

var dragOver = function (e) {
    e.preventDefault();
    this.classList.add('overlay');
};

var dragLeave = function (e) {
    this.classList.remove('overlay');
};


var dragDrop = function (e) {
    this.classList.remove('overlay');
    window.alert('Dropped');
};

var dropArea = document.getElementById('box');

dropArea.addEventListener('dragover', dragOver, false);
dropArea.addEventListener('dragleave', dragLeave, false);
dropArea.addEventListener('drop', dragDrop, false);

CSS

这个after规则将为可掉落区域创建一个完全覆盖的覆盖层。

#box.overlay:after {
    content:'';
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 1;
}

这里是完整的解决方案:http://jsfiddle.net/F6GDq/8/

我希望它能帮助有同样问题的人。

下面是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!');
                    }
                })

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

Yes.

#drop * {pointer-events: none;}

CSS似乎足够Chrome。

当使用它与Firefox, #drop不应该有文本节点直接(否则有一个奇怪的问题,一个元素“离开它自己”),所以我建议只留下一个元素(例如,使用一个div在#drop里面放所有的东西)

下面是一个jsfiddle解决原始问题(损坏的)示例。

我还从@Theodore Brown的例子中做了一个简化版本,但只基于这个CSS。

不过,并不是所有浏览器都实现了这种CSS: http://caniuse.com/pointer-events

看到Facebook的源代码,我可以找到这个指针事件:无;但是,它可能多次与优雅的降级回退一起使用。至少它是如此简单,解决了许多环境中的问题。

下面是另一种基于事件时间的方法。

从子元素分派的dragenter事件可以被父元素捕获,并且它总是发生在dragleave之前。这两个事件之间的时间间隔非常短,比任何可能的人类鼠标操作都要短。所以,这个想法是记住dragenter发生的时间,过滤dragleave事件发生“不是太快”之后…

这个简短的例子适用于Chrome和Firefox:

var node = document.getElementById('someNodeId'),
    on   = function(elem, evt, fn) { elem.addEventListener(evt, fn, false) },
    time = 0;

on(node, 'dragenter', function(e) {
    e.preventDefault();
    time = (new Date).getTime();
    // Drag start
})

on(node, 'dragleave', function(e) {
    e.preventDefault();
    if ((new Date).getTime() - time > 5) {
         // Drag end
    }
})