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 ]

6 голосов
/ 31 января 2016

Вот еще одно решение, использующее 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');
   }
}

Надеюсь, это работает, вот скрипка .

5 голосов
/ 14 ноября 2013

Я написал небольшую библиотеку под названием Dragster для решения этой конкретной проблемы, работает везде, кроме тихого бездействия в IE (который не поддерживает конструкторы событий DOM, но было бы довольно легко напишите что-нибудь подобное, используя пользовательские события jQuery)

5 голосов
/ 04 июля 2018

Простым решением является добавление правила css pointer-events: none к дочернему компоненту, чтобы предотвратить срабатывание ondragleave. Смотрите пример:

function enter(event) {
  document.querySelector('div').style.border = '1px dashed blue';
}

function leave(event) {
  document.querySelector('div').style.border = '';
}
div {
  border: 1px dashed silver;
  padding: 16px;
  margin: 8px;
}

article {
  border: 1px solid silver;
  padding: 8px;
  margin: 8px;
}

p {
  pointer-events: none;
  background: whitesmoke;
}
<article draggable="true">drag me</article>

<div ondragenter="enter(event)" ondragleave="leave(event)">
  drop here
  <p>child not triggering dragleave</p>
</div>
4 голосов
/ 18 февраля 2014

У меня возникла та же проблема, и я попытался использовать решение 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/

Я надеюсь, что это поможет любому с той же проблемой.

4 голосов
/ 19 сентября 2012

Не уверен, что это кросс-браузер, но я тестировал в Chrome и он решает мою проблему:

Я хочу перетащить файл на всю страницу, но моя перетаскивание срабатывает, когда я перетаскиваю дочерний элемент. Мое решение было посмотреть на х и у мыши:

У меня есть 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;
});
4 голосов
/ 02 марта 2019

Очень простое решение:

parent.addEventListener('dragleave', function(evt) {
    if (!parent.contains(evt.relatedTarget)) {
        // Here it is only dragleave on the parent
    }
}
4 голосов
/ 09 июня 2017

Просто проверьте, является ли перетаскиваемый элемент дочерним, если это так, то не удаляйте свой класс стиля dragover. Довольно просто и работает для меня:

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })
3 голосов
/ 11 июня 2013

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

Я буду использовать 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 на дочернем объекте срабатывает до onLeave на родительском, мы задерживаем его, слегка изменяя порядок, поэтому сначала класс удаляется, а затем повторно вызывается через миллисекунду.

2 голосов
/ 19 января 2019

Это довольно простое решение работает для меня до сих пор, при условии, что ваше событие прикреплено к каждому элементу перетаскивания индивидуально.

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}
2 голосов
/ 03 июля 2013

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