HTML5 dragleave срабатывает при наведении на дочерний элемент - PullRequest
258 голосов
/ 18 августа 2011

У меня проблема в том, что событие 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");
                 }
                });

То, что он должен сделать, это уведомить пользователя, сделав каплю div красным при перетаскивании чего-либо туда. Это работает, но если вы перетащите в дочерний элемент p, dragleave будет запущен, а div больше не будет красным. Возвращение к падению div также не делает его снова красным. Необходимо полностью выйти из капли div и снова перетащить в нее, чтобы она стала красной.

Можно ли предотвратить запуск dragleave при перетаскивании в дочерний элемент?

2017 Обновление: TL; DR, найдите CSS pointer-events: none;, как описано в ответе @ H.D. ниже, который работает в современных браузерах и IE11.

Ответы [ 31 ]

2 голосов
/ 14 июля 2013

Альтернативный рабочий раствор, немного проще.

//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) {
2 голосов
/ 17 июля 2016

Я написал модуль перетаскивания под названием drip-drop , который, среди прочего, исправляет это странное поведение.Если вы ищете хороший низкоуровневый модуль перетаскивания, который вы можете использовать в качестве основы для чего-либо (загрузка файла, перетаскивание в приложении, перетаскивание из или на внешние источники), вам следует проверить этомодуль out:

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
    }
});
1 голос
/ 16 октября 2018
Событие

" dragleave " наступает, когда указатель мыши покидает область перетаскивания целевого контейнера.

Это имеет большой смысл, поскольку во многих случаях может быть только родитель , а не его потомки. Я думаю, event.stopPropogation () должен был обработать этот случай, но похоже, что он не сработал.

Вышеупомянутые решения, кажется, работают в большинстве случаев, но не работают в случае тех дочерних элементов, которые не поддерживают dragenter / dragleave события, такие как IFrame .

1 Обходной путь - проверить event.relatedTarget и убедиться, что он находится внутри контейнера, а затем проигнорировать событие dragleave , как я это сделал здесь:

function isAncestor(node, target) {
    if (node === target) return false;
    while(node.parentNode) {
        if (node.parentNode === target)
            return true;
        node=node.parentNode;
    }
    return false;
}

var container = document.getElementById("dropbox");
container.addEventListener("dragenter", function() {
    container.classList.add("dragging");
});

container.addEventListener("dragleave", function(e) {
    if (!isAncestor(e.relatedTarget, container))
        container.classList.remove("dragging");
});

Вы можете найти рабочую скрипку здесь !

1 голос
/ 01 декабря 2014

У меня была похожая проблема - мой код для сокрытия события dropzone при перетаскивании тела bodyatantly запускался при наведении на дочерние элементы, вызывая мерцание dropzone в Google Chrome.функция для скрытия dropzone вместо того, чтобы вызывать ее сразу.Затем, если сработает другой драговер или драглэйв, вызов функции по расписанию отменяется.

body.addEventListener('dragover', function() {
    clearTimeout(body_dragleave_timeout);
    show_dropzone();
}, false);

body.addEventListener('dragleave', function() {
    clearTimeout(body_dragleave_timeout);
    body_dragleave_timeout = setTimeout(show_upload_form, 100);
}, false);

dropzone.addEventListener('dragover', function(event) {
    event.preventDefault();
    dropzone.addClass("hover");
}, false);

dropzone.addEventListener('dragleave', function(event) {
    dropzone.removeClass("hover");
}, false);
0 голосов
/ 24 июля 2014

Вы можете использовать тайм-аут с флагом transitioning и прослушивать верхний элемент.dragenter / dragleave от дочерних событий будет пузыриться до контейнера.

Поскольку dragenter на дочернем элементе срабатывает до dragleave контейнера, мы установим флаг show как переходящий на 1 мс... прослушиватель 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/

0 голосов
/ 01 мая 2013

Вот еще один подход, основанный на синхронизации событий.

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

Этот короткий пример работает на Chrome и Firefox:

var node = document.getElementById('someNodeId'),
    on   = function(elem, evt, fn) { elem.addEventListener(evt, fn, false) },
    time = 0;

on(node, 'dragenter', function(e) {
    e.preventDefault();
    time = (new Date).getTime();
    // Drag start
})

on(node, 'dragleave', function(e) {
    e.preventDefault();
    if ((new Date).getTime() - time > 5) {
         // Drag end
    }
})
0 голосов
/ 13 февраля 2018

Я нашел похожее, но более элегантное решение для ответа @ 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.

0 голосов
/ 05 июля 2016

Просто попробуйте использовать 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');

  }
})

Guido

0 голосов
/ 20 июня 2016

используйте этот код 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');
             }
        }
    });
0 голосов
/ 10 июня 2015

Вам необходимо удалить события указателя для всех дочерних объектов цели перетаскивания.

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) {
                //
            }
        }
    }
...