如何在窗口外拖动时检测Firefox中的dragleave事件

时间:2012-04-20 21:17:28

标签: javascript jquery html5 drag-and-drop

在窗口外拖动时,Firefox无法正确触发dragleave事件:

https://bugzilla.mozilla.org/show_bug.cgi?id=665704

https://bugzilla.mozilla.org/show_bug.cgi?id=656164

我正在尝试为此开发一种解决方法(我知道这可能是因为Gmail正在这样做),但我能想出的唯一一件事看起来真的很虚伪。

一种了解何时在窗口外拖动的方法是等待dragover事件停止触发(因为dragover在拖放操作期间不断触发)。我正是这样做的:

var timeout;

function dragleaveFunctionality() {
  // do stuff
}

function firefoxTimeoutHack() {
  clearTimeout(timeout);
  timeout = setTimeout(dragleaveFunctionality, 200);
}

$(document).on('dragover', firefoxTimeoutHack);

此代码实质上是一遍又一遍地创建和清除超时。除非dragover事件停止触发,否则将无法达到200毫秒的超时。

虽然这有效,但我不喜欢为此目的使用超时的想法。感觉不对。这也意味着在“掉落区”造型消失之前会有一点滞后。

我的另一个想法是检测鼠标何时离开窗口,但在拖放操作期间,这样做的正常方法似乎不起作用。

有没有人有更好的方法呢?

更新

这是我正在使用的代码:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Drag and Drop Issue</title>
  <script src="http://code.jquery.com/jquery.js"></script>
</head>
<body>
  Open up the console and look at what number is reporting when dragging files in and out of the window. The number should always be 0 when leaving the window, but in Firefox it's not.
  <script type="text/javascript">
    $(function() {
      var counter = 0;
      $(document).on('dragenter', function(e) {
        counter += 1;
        console.log(counter, e.target);
      });
      $(document).on('dragleave', function(e) {
        counter -= 1;
        console.log(counter, e.target);
      });
    });
  </script>  
</body>
</html>

5 个答案:

答案 0 :(得分:57)

我找到了解决方案。问题不在于dragleave事件没有发生;相反,dragenter事件在第一次将文件拖入窗口时触发了两次(另外有时在拖动某些元素时)。我最初的解决方案是使用计数器来跟踪最终dragleave事件发生的时间,但dragenter事件的双重触发正在弄乱计数。 (为什么我不能只听你问的dragleave?好吧,因为dragleave的功能与mouseout非常相似,因为它不仅在离开元素时触发,而且在进入时也会触发因此,当dragleave触发时,你的鼠标可能仍然在原始元素的范围内。)

我想出的解决方案是跟踪触发了哪些元素dragenterdragleave。由于事件传播到文档,因此在特定元素上侦听dragenterdragleave不仅会捕获该元素上的事件,还会捕获其子元素上的事件。

因此,我创建了一个jQuery集合$()来跟踪在哪些元素上触发的事件。每当dragenter被解雇时,我都会将event.target添加到集合中,并且每当dragleave发生时我都会从集合中删除event.target。这个想法是,如果集合是空的,那就意味着我实际上已经离开了原始元素,因为如果我输入了一个子元素,那么至少有一个元素(子元素)仍然在jQuery集合中。最后,当drop事件被触发时,我想将集合重置为空,以便在下一个dragenter事件发生时准备就绪。

jQuery还节省了大量额外的工作,因为它会自动进行重复检查,因此即使Firefox错误地双重调用event.targetdragenter也不会被添加两次。

Phew,无论如何,这是我最终使用的代码的基本版本。如果其他人有兴趣使用它,我已将它放入一个简单的jQuery插件中。基本上,您在任何元素上调用.draghover,并且在第一次拖入元素时会触发draghoverstart,并且一旦拖动实际离开它就会触发draghoverend

// The plugin code
$.fn.draghover = function(options) {
  return this.each(function() {

    var collection = $(),
        self = $(this);

    self.on('dragenter', function(e) {
      if (collection.length === 0) {
        self.trigger('draghoverstart');
      }
      collection = collection.add(e.target);
    });

    self.on('dragleave drop', function(e) {
      collection = collection.not(e.target);
      if (collection.length === 0) {
        self.trigger('draghoverend');
      }
    });
  });
};

// Now that we have a plugin, we can listen for the new events 
$(window).draghover().on({
  'draghoverstart': function() {
    console.log('A file has been dragged into the window.');
  },
  'draghoverend': function() {
    console.log('A file has been dragged out of window.');
  }
});

答案 1 :(得分:3)

根据您希望实现的目标,您可以使用仅在Firefox中可用的:-moz-drag-over伪类来解决此问题,该伪类允许您对拖动到元素上的文件做出反应。

看看这个简单的演示http://codepen.io/ryanseddon/pen/Ccsua

.dragover {
    background: red;
    width: 500px;
    height: 300px;
}
.dragover:-moz-drag-over {
    background: green;
}

答案 2 :(得分:0)

addEvent(document, "mouseout", function(e) {
    e = e ? e : window.event;
    var from = e.relatedTarget || e.toElement;
    if (!from || from.nodeName == "HTML") {
        // stop your drag event here
        // for now we can just use an alert
        alert("left window");
    }
});

这是从How can I detect when the mouse leaves the window?复制的。 addEvent只是crossbrowser addEventListener。

答案 3 :(得分:0)

受@PhilipWalton代码的启发,我简化了jQuery插件代码。

$.fn.draghover = function(fnIn, fnOut) {
    return this.each(function() {
        var n = 0;
        $(this).on('dragenter', function(e) {
            (++n, n==1) && fnIn && fnIn.call(this, e);
        }).on('dragleave drop', function(e) {
            (--n, n==0) && fnOut && fnOut.call(this, e);
        });
    });
};

现在你可以像jquery hover方法一样使用jquery插件:

// Testing code 1
$(window).draghover(function() {
    console.log('into window');
}, function() {
    console.log('out of window');
});

// Testing code 2
$('#d1').draghover(function() {
    console.log('into #d1');
}, function() {
    console.log('out of #d1');
});

答案 4 :(得分:0)

只有对我有用的解决方案并带给我一些希望这有助于某人!

请注意,克隆时您需要深入克隆事件和数据:

HTML:

<div class="dropbox"><p>Child element still works!</p></div>

<div class="dropbox"></div>

<div class="dropbox"></div>

的jQuery

$('.dropbox').each(function(idx, el){
    $(this).data("counter" , 0);
});

$('.dropbox').clone(true,true).appendTo($('body');

$('dropbox').on({
    dragenter : function(e){
        $(this).data().counter++;
        <!-- YOUR CODE HERE -->
    },
      dragleave: function(e){

        $(this).data().counter--;

         if($(this).data().counter === 0)
              <!-- THEN RUN YOUR CODE HERE -->
    }
});