javascript js鼠标悬停事件 - 悬停子元素时会触发HTML5 dragleave




jquery鼠标hover hover效果 (23)

use this code http://jsfiddle.net/HU6Mk/258/ :

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

         dragleave: function(event) {
             var x = event.clientX, y = event.clientY,
                 elementMouseIsOver = document.elementFromPoint(x, y);
             if(!$(elementMouseIsOver).closest('.red').length) {
                 $(this).removeClass('red');
             }
        }
    });

我遇到的问题是,当悬停该元素的子元素时,会触发元素的dragleave事件。 另外,当再次悬停父元素时, dragenter不会被触发。

我做了一个简化小提琴: http://jsfiddle.net/pimvdb/HU6Mk/1/ : 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");
                 }
                });

它应该做的是通过在拖动某些东西时将其放置为红色来通知用户。 这是有效的,但是如果你拖入p小孩, dragleave被激发, div不再是红色。 移回放置div也不会再次变红。 有必要完全移出div并再次将其拖回红色。

拖入子元素时是否可以防止dragleave被触发?

2017更新: TL; DR,查找CSS pointer-events: none; 如@ HD下面的答案中所述,它适用于现代浏览器和IE11。


如果您使用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


这是另一个使用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');
   }
}

希望这有效,这是一个fiddle


另一种工作解决方案,更简单一点。

//Note: Due to a bug with Chrome the 'dragleave' event is fired when hovering the dropzone, then
//      we must check the mouse coordinates to be sure that the event was fired only when 
//      leaving the window.
//Facts:
//  - [Firefox/IE] e.originalEvent.clientX < 0 when the mouse is outside the window
//  - [Firefox/IE] e.originalEvent.clientY < 0 when the mouse is outside the window
//  - [Chrome/Opera] e.originalEvent.clientX == 0 when the mouse is outside the window
//  - [Chrome/Opera] e.originalEvent.clientY == 0 when the mouse is outside the window
//  - [Opera(12.14)] e.originalEvent.clientX and e.originalEvent.clientY never get
//                   zeroed if the mouse leaves the windows too quickly.
if (e.originalEvent.clientX <= 0 || e.originalEvent.clientY <= 0) {

一个非常简单的解决方案是使用pointer-events CSS属性 。 只需在每个子元素的dragstart上将其值设置为none 。 这些元素不会再触发与鼠标相关的事件,因此它们不会将鼠标捕捉到它们上面,因此不会触发父上的dragleave

不要忘记在完成拖动时将此属性重新设置为auto ;)


pimvdb ..

你为什么不尝试用drop而不是dragleave 。 它为我工作。 希望这能解决你的问题。

请检查jsFiddle: http://jsfiddle.net/HU6Mk/118/ : http://jsfiddle.net/HU6Mk/118/

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

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

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

问题是当鼠标移动到子元素前面时, dragleave事件被触发。

我已经尝试了各种方法来检查e.target元素是否与this元素相同,但无法获得任何改进。

我解决这个问题的方式有点破解,但可以100%工作。

dragleave: function(e) {
               // Get the location on screen of the element.
               var rect = this.getBoundingClientRect();

               // Check the mouseEvent coordinates are outside of the rectangle
               if(e.x > rect.left + rect.width || e.x < rect.left
               || e.y > rect.top + rect.height || e.y < rect.top) {
                   $(this).removeClass('red');
               }
           }

拖入子元素时是否可以防止dragleave被触发?

是。

#drop * {pointer-events: none;}

对于Chrome来说,这个CSS似乎已经足够了。

在Firefox中使用它时,#drop不应该直接具有文本节点(否则会出现一个奇怪的问题,因为元素“自己留下” ),所以我建议只保留一个元素(例如,在里面使用div #drop把所有内容)

这是一个解决http://jsfiddle.net/pimvdb/HU6Mk/1/ 的jsfiddle

我还制作了@Theodore Brown示例中分支的简化版本 ,但仅基于此CSS。

不是所有的浏览器都实现了这个CSS: http://caniuse.com/pointer-events : http://caniuse.com/pointer-events

看到Facebook的源代码,我可以找到这个pointer-events: none; 几次,但它可能与优雅的退化回退一起使用。 至少它非常简单并且解决了很多环境中的问题。


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

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

我写了一个名为Dragster的小程序库来处理这个确切的问题,除了在IE中静静地做任何事(它不支持DOM事件构造函数,但使用jQuery的自定义事件编写类似代码很容易)


我偶然发现了同样的问题,这里是我的解决方案 - 我认为这比以上更容易。 我不确定它是否是交叉浏览器(可能取决于冒泡命令)

为了简单起见,我将使用jQuery,但解决方案应该独立于框架。

事件泡沫给父母任何方式如此给予:

<div class="parent">Parent <span>Child</span></div>

我们附加活动

el = $('.parent')
setHover = function(){ el.addClass('hovered') }
onEnter  = function(){ setTimeout(setHover, 1) }
onLeave  = function(){ el.removeClass('hovered') } 
$('.parent').bind('dragenter', onEnter).bind('dragleave', onLeave)

这就是它。 :)它的工作原理是,即使onEnter on child在onLeave on parent之前触发,我们会稍微推迟它的顺序,所以class先被移除,然后在一毫秒后重新获得。


You need to remove the pointer events for all child objects of the drag target.

function disableChildPointerEvents(targetObj) {
        var cList = parentObj.childNodes
        for (i = 0; i < cList.length; ++i) {
            try{
                cList[i].style.pointerEvents = 'none'
                if (cList[i].hasChildNodes()) disableChildPointerEvents(cList[i])
            } catch (err) {
                //
            }
        }
    }

试着使用event.eventPhase。它只会在目标被输入时设置为2(Event.AT_TARGET),否则设置为3(Event.BUBBLING_PHASE)。

我使用了eventPhase来绑定或取消绑定dragleave事件。

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

  if(e.eventPhase === Event.AT_TARGET) {

    $('.dropzone').addClass('drag-over');

    $('.dropzone').on('dragleave', function(e) {
      $('.dropzone').removeClass('drag-over');
    });

  }else{

    $('.dropzone').off('dragleave');

  }
})

圭多


解决此问题的“正确”方法是禁用放置目标的子元素上的指针事件(如@ HD的答案中)。 这是我创建的一个jsFiddle,它演示了这种技术 。 不幸的是,这在IE11之前的Internet Explorer版本中不起作用,因为它们http://caniuse.com/pointer-events

幸运的是,我能够想出一个解决方案,可以在旧版本的IE中运行。 基本上,它涉及识别并忽略在拖拽子元素时发生的拖曳事件。 因为dragenter事件在父节点上的dragleave事件之前在子节点上dragleave ,所以可以将单独的事件侦听器添加到每个子节点,这些子节点会从放置目标添加或移除“ignore-drag-leave”类。 然后,放置目标的dragleave事件侦听器可以简单地忽略此类存在时发生的调用。 这是一个jsFiddle演示这个解决方法 。 它经过测试并在Chrome,Firefox和IE8 +上运行。

更新:

我创建了一个jsFiddle来演示一个使用特性检测的组合解决方案 ,如果支持(当前为Chrome,Firefox和IE11),则使用指针事件,如果指针事件支持不可用,浏览器将回退到向子节点添加事件(IE8 -10)。


我遇到了同样的问题,并尝试使用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

此规则将为可投放区域创建一个完全覆盖的叠加层。

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

以下是完整的解决方案: http://jsfiddle.net/F6GDq/8/ : 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!');
                    }
                })

花了这么多小时后,我得到了这个建议完全按照预期工作。 我只想在文件被拖动时提供提示,并且文档dragover,dragleave在Chrome浏览器上造成痛苦的闪烁。

这就是我解决这个问题的方法,也为用户提供了适当的线索。

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

不知道这个跨浏览器,但我在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;
});

我发现类似的,但更优雅的解决方案@ azlar的答案,这是我的解决方案:

$(document).on({
    dragenter: function(e) {
        e.stopPropagation();
        e.preventDefault();
        $("#dragging").show();
    },
    dragover: function(e) {
        e.stopPropagation();
        e.preventDefault();
    },
    dragleave: function(e) {
        e.stopPropagation();
        e.preventDefault();
        if (e.clientX <= 0 ||
            // compare clientX with the width of browser viewport
            e.clientX >= $(window).width() ||
            e.clientY <= 0 ||
            e.clientY >= $(window).height())
            $("#dragging").hide();
    }
});

该方法检测鼠标是否已经离开页面。它在Chrome和Edge中运行良好。


在这里,最简单的跨浏览器解决方案(严重):

jsfiddle < - 尝试拖动框内的某个文件

你可以做这样的事情:

var dropZone= document.getElementById('box');
var dropMask = document.getElementById('drop-mask');

dropZone.addEventListener('dragover', drag_over, false);
dropMask.addEventListener('dragleave', drag_leave, false);
dropMask.addEventListener('drop', drag_drop, false);

简而言之,您可以在dropzone内创建一个“蒙版”,并继承宽度和高度,绝对位置,这将在dragover启动时显示。
所以,在显示那个面具之后,你可以通过附加其他的dragleave和放置事件来实现它。

离开或放下后,您只需再次隐藏面罩。
简单,没有并发症。

(Obs .:格雷格佩蒂特建议 - 你必须确保面具悬停在整个盒子上,包括边框)


你可以通过jQuery源代码中的一点灵感来修复它:

dragleave: function(e) {
    var related = e.relatedTarget,
        inside = false;

    if (related !== this) {

        if (related) {
            inside = jQuery.contains(this, related);
        }

        if (!inside) {

            $(this).removeClass('red');
        }
    }

}

不幸的是,它不适用于Chrome,因为dragleave似乎并不存在于dragleave事件中,并且我认为您使用的是Chrome,因为您的示例在Firefox中dragleave这是一个上面的代码实现的版本


只需检查拖动的元素是否为小孩,如果是,则不要删除“dragover”风格类。 很简单,适用于我:

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })

CSS字符转义序列(在选择器中使用)很tricky 。 他们是如此棘手我甚至做了一个网络应用程序,可以告诉你如何逃避CSS中的任何角色

简单地选择所有option元素然后根据它们的value属性值filter它们会更简单更有效:

var value = this.value;
$select.find('option').filter(function() {
  return this.value == value;
}).show();

这样,根本不需要特殊的转义。





javascript jquery html5 javascript-events drag-and-drop