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

36

当使用Firefox浏览器拖动窗口外时,dragleave事件无法正常触发:

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

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

我正在尝试开发一种解决方法(因为Gmail可以做到),但我能想到的唯一解决方法似乎非常hackish。

一种判断是否拖动超出窗口的方式是等待 dragover 事件停止触发(因为 dragover 在整个拖放操作期间会一直触发)。这是我的做法:

var timeout;

function dragleaveFunctionality() {
  // do stuff
}

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

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

这段代码本质上是反复创建和清除一个超时计时器。除非 dragover 事件停止触发,否则不会达到200毫秒的超时时间。

虽然这样做有效,但我不喜欢使用超时计时器来实现这个目的。感觉不太对。这也意味着在“放置区域”样式消失之前会有轻微的延迟。

我想到的另一个方法是检测鼠标离开窗口的时间,但在拖放操作期间,常规方法似乎无法实现。

是否有其他更好的方法可以实现这一点呢?

更新:

这是我正在使用的代码:

 $(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 src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>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.</p>


问题似乎在Firefox 11中已经修复 - 您的目标版本是哪个? - Eli Sand
我仍然在Firefox 11上看到它,根据错误报告,它还没有被修复。我会看看是否可以制作一个演示来链接。 - Philip Walton
如果您在Firefox中以安全模式(无插件)启动会怎样?我刚刚按原样尝试了您的演示页面,输入时得到了1,退出时得到了0,就像应该的那样。 - Eli Sand
@EliSand 我不知道为什么你看不到它。我已经在Windows 7x64上尝试了FF11,但我仍然看到问题。bind()on()之类的因素与此无关。 - Philip Walton
@PhilipWalton - 我也不知道。到目前为止,我的所有想法都已经用完了。我提到bind()on()只是为了抓住任何可能的最后一根救命稻草-你永远不知道…… - Eli Sand
显示剩余4条评论
5个回答

58

我找到了一个解决方案。问题不在于 dragleave 事件没有触发,而是在第一次拖入文件时(以及有时拖动到某些元素上方时)dragenter 事件会触发两次。我的原始解决方案是使用计数器来跟踪最终的 dragleave 事件何时发生,但是 dragenter 事件的双重触发打乱了计数。 (你为什么不能只监听 dragleave 呢?因为 dragleave 的功能与 mouseout 非常相似,它不仅在离开元素时触发,还在进入子元素时触发。因此,当 dragleave 触发时,你的鼠标可能仍然在原始元素的边界内。)

我想出了一个解决方案,就是跟踪哪些元素已经触发了 dragenterdragleave 事件。由于事件向上冒泡到文档,因此在特定元素上侦听 dragenterdragleave 将捕获不仅该元素上的事件,还包括其子元素上的事件。

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

jQuery 还可以节省很多额外的工作,因为它自动执行重复检查,所以即使 Firefox 错误地双倍调用 dragenterevent.target 也不会被添加两次。

总之,这是我最终使用的代码的基本版本。我放入了一个简单的 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.');
  }
});

无需 jQuery

您可以像这样处理,而无需使用 jQuery:

// I want to handle drag leaving on the document
let count = 0
onDragEnter = (event) => {
  if (event.currentTarget === document) {
    count += 1
  }
}

onDragLeave = (event) => {
  if (event.currentTarget === document) {
     count += 0
  }

  if (count === 0) {
    // Handle drag leave.
  }
}

1
我在dragleave处理程序中添加了drop,因为这也应该被视为draghoverend。带有注释的Gist:https://gist.github.com/3794126 - meleyal
这对我来说在Chrome和Firefox中似乎有点buggy。页面上似乎有一些元素我还没有确定会触发一个离开事件。然而,与vanilla dragleave实现相比,它确实可以使问题更容易解决。如果我能解决它,我会回报的。 - DanH
我遇到的问题是,在将文件拖动到文本节点上时,Firefox会触发“draghoverend”事件。这里有一个演示 fiddle:http://jsfiddle.net/tusRy/4/。但是,该插件在进入/离开窗口方面完美地工作。 - DanH
@meleyal,实际上你是完全正确的!我在我正在处理的一个项目中遇到了你指出的问题。我已经更新了答案以反映这一点。谢谢! - Philip Walton
3
这种方法仍然可行,但解决方案本身需要进行轻微更改。具体来说,所有对e.target的引用都需要改为e.originalEvent.target。这解决了Firefox中#text元素(p标签的内容)触发dragenter/dragleave事件的问题。jQuery事件将这些引用规范化为<p>,这会导致此处建议的制表符方案出现问题。通过这种更改,dragend/dragleave事件似乎可以按预期工作。 - kamelkev
显示剩余4条评论

3

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

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

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

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

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

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

这是从如何检测鼠标离开窗口?复制的。addEvent只是跨浏览器的addEventListener。


2
不行,这个方法不起作用,也不应该被点赞。正如我在问题中所说的,典型的鼠标事件在拖放操作期间不会触发。 - Philip Walton
没有看到关于典型事件未触发的那部分。 - pbfy0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接