我遇到的问题是,一个元素的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中都能运行。


当前回答

您可以使用带有转换标志的超时并监听顶部元素。子事件中的Dragenter / dragleave将会弹出到容器中。

由于子元素的dragenter在容器的dragleave之前触发,我们将把标志显示设置为过渡1ms…dragleave监听器将在1毫秒结束之前检查标志。

该标志仅在转换到子元素时为真,而在转换到(容器的)父元素时为假。

var $el = $('#drop-container'),
    transitioning = false;

$el.on('dragenter', function(e) {

  // temporarily set the transitioning flag for 1 ms
  transitioning = true;
  setTimeout(function() {
    transitioning = false;
  }, 1);

  $el.toggleClass('dragging', true);

  e.preventDefault();
  e.stopPropagation();
});

// dragleave fires immediately after dragenter, before 1ms timeout
$el.on('dragleave', function(e) {

  // check for transitioning flag to determine if were transitioning to a child element
  // if not transitioning, we are leaving the container element
  if (transitioning === false) {
    $el.toggleClass('dragging', false);
  }

  e.preventDefault();
  e.stopPropagation();
});

// to allow drop event listener to work
$el.on('dragover', function(e) {
  e.preventDefault();
  e.stopPropagation();
});

$el.on('drop', function(e) {
  alert("drop!");
});

jsfiddle: http://jsfiddle.net/ilovett/U7mJj/

其他回答

不确定这是否跨浏览器,但我在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;
});

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

Yes.

#drop * {pointer-events: none;}

CSS似乎足够Chrome。

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

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

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

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

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

   private onDragLeave(e) {
     e.preventDefault();
     e.stopPropagation();

    if (this.isEventFromChild(e)) {
      return;
    }

    this.isDragOver = false;
  }
  
  private isEventFromChild(e): boolean {
    //relatedTarget is null on Safari, so we take it from coordinates
    const relatedTarget = e.relatedTarget || document.elementFromPoint(e.clientX, e.clientY);
    return e.currentTarget.contains(relatedTarget);
  }

我编写了一个名为drop -drop的拖放模块来修复这种奇怪的行为。如果你正在寻找一个好的低级拖放模块,你可以用作任何事情的基础(文件上传,应用程序内拖放,从或从外部源拖放),你应该检查这个模块:

https://github.com/fresheneesz/drip-drop

这是你在点滴中尝试做的事情:

$('#drop').each(function(node) {
  dripDrop.drop(node, {
    enter: function() {
      $(node).addClass('red')  
    },
    leave: function() {
      $(node).removeClass('red')
    }
  })
})
$('#drag').each(function(node) {
  dripDrop.drag(node, {
    start: function(setData) {
      setData("text", "test") // if you're gonna do text, just do 'text' so its compatible with IE's awful and restrictive API
      return "copy"
    },
    leave: function() {
      $(node).removeClass('red')
    }
  })
})

为了在没有库的情况下做到这一点,我在滴漏中使用了反制技术,尽管评分最高的答案错过了重要的步骤,这将导致除了第一个滴漏之外的所有东西都被打破。以下是正确的做法:

var counter = 0;    
$('#drop').bind({
    dragenter: function(ev) {
        ev.preventDefault()
        counter++
        if(counter === 1) {
          $(this).addClass('red')
        }
    },

    dragleave: function() {
        counter--
        if (counter === 0) { 
            $(this).removeClass('red');
        }
    },
    drop: function() {
        counter = 0 // reset because a dragleave won't happen in this case
    }
});

我知道这是一个老问题,但我想补充我的偏好。我处理这个通过添加类触发css:后元素在更高的z-index然后你的内容。这样可以过滤掉所有的垃圾。

.droppable{
    position: relative;
    z-index: 500;
}

.droppable.drag-over:after{
    content: "";
    display:block;
    position:absolute;
    left:0;
    right:0;
    top:0;
    bottom:0;
    z-index: 600;
}

然后只需在您的第一个dragenter事件上添加拖拽类,并且不再有子元素触发该事件。

dragEnter(event){
 dropElement.classList.add('drag-over');
}

dragLeave(event){
 dropElement.classList.remove('drag-over');
}