Как обнаружить событие dragleave в Firefox при перетаскивании за пределы окна

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

Этот код по существу создает и очищает тайм-аут снова и снова. Таймаут в 200 миллисекунд не будет достигнут, если событие dragover не прекратит стрельбу.

Пока это работает, мне не нравится идея использовать таймаут для этой цели. Он чувствует себя не так. Это также означает небольшое отставание до того, как стилизация "dropzone" исчезнет.

Другая идея, которую я имел, - обнаружить, когда мышь покидает окно, но обычные способы сделать это не работают во время операций перетаскивания.

Есть ли у кого-нибудь лучший способ сделать это?

UPDATE:

Вот код, который я использую:

<!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 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>

Ответ 1

Я нашел решение. Проблема была не столько в том, что событие dragleave не стреляло; скорее, событие dragenter срабатывало дважды при первом перетаскивании файла в окно (а иногда и при перетаскивании определенных элементов). Моим первоначальным решением было использование счетчика для отслеживания, когда происходило окончательное событие dragleave, но двойное срабатывание событий dragenter было испорченным подсчетом. (Почему я не мог просто слушать dragleave, который вы спрашиваете? Ну, потому что dragleave работает очень похоже на mouseout тем, что он срабатывает не только при выходе из элемента, но и при вводе дочернего элемента. Таким образом, когда dragleave, ваша мышь может очень хорошо находиться в пределах исходного элемента.)

Решение, с которым я столкнулся, состояло в том, чтобы отслеживать, какие элементы dragenter и dragleave были задействованы. Поскольку события распространяются до документа, прослушивание dragenter и dragleave для определенного элемента будет захватывать не только события этого элемента, но также события на его дочерних элементах.

Итак, я создал коллекцию jQuery $(), чтобы отслеживать, какие события были запущены на каких элементах. Я добавил event.target в коллекцию всякий раз, когда был запущен dragenter, и я удалил event.target из коллекции всякий раз, когда произошла ошибка. Идея заключалась в том, что если коллекция была пустой, это означало бы, что я фактически оставил исходный элемент, потому что, если бы я ввел дочерний элемент, по крайней мере один элемент (дочерний элемент) все равно был бы в коллекции jQuery. Наконец, когда событие drop запущено, я хочу, чтобы reset коллекция была пуста, поэтому она готова к работе, когда произойдет следующее событие dragenter.

jQuery также сохраняет много дополнительной работы, потому что он автоматически выполняет повторную проверку, поэтому event.target не добавляется дважды, даже если Firefox был неправильно двойным вызовом dragenter.

В любом случае, вот базовая версия кода, который я использовал. Я включил его в простой плагин 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.');
  }
});

Ответ 2

В зависимости от того, что вы хотите выполнить, вы можете обойти эту проблему, используя псевдокласс класса :-moz-drag-over, доступный только в Firefox, который позволяет вам реагировать на файл, перетаскиваемый над элементом.

Взгляните на это простое демо http://codepen.io/ryanseddon/pen/Ccsua

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

Ответ 3

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 - это просто crossbrowser addEventListener.

Ответ 4

Вдохновленный кодом @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, например, метод наложения курсора 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');
});

Ответ 5

Единственное решение, которое сработало для меня и привлекло меня, похоже, что это помогает кому-то!

Обратите внимание, что при клонировании вам необходимо выполнить глубокое исследование событий и данных:

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