HTML5 dragleave срабатывает при наведении курсора на дочерний элемент

Проблема, с которой я столкнулся, заключается в том, что событие 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;, как описано в ответе @HD ниже, который работает в современных браузерах и IE11.


person pimvdb    schedule 18.08.2011    source источник
comment
Ошибка, о которой сообщает pimvdb, все еще существует в Webkit по состоянию на май 2012 года. Я противодействовал ей, также добавив класс в dragover, что далеко не так хорошо, поскольку срабатывает так часто, но, похоже, немного исправляет проблему.   -  person ajm    schedule 11.05.2012
comment
@ajm: Спасибо, в какой-то степени это работает. Однако в Chrome возникает вспышка при входе в дочерний элемент или выходе из него, предположительно потому, что в этом случае dragleave все еще запускается.   -  person pimvdb    schedule 11.05.2012
comment
Я открыл ошибку пользовательского интерфейса jQuery голоса за приветствуются, чтобы они могли решите вложить в это ресурсы   -  person fguillen    schedule 21.08.2012
comment
@fguillen: Мне очень жаль, но это не имеет ничего общего с jQuery UI. Фактически, jQuery даже не нужен, чтобы вызвать ошибку. Я уже зарегистрировал ошибку WebKit, но на данный момент нет обновлений.   -  person pimvdb    schedule 21.08.2012
comment
@pimvdb, да, я видел ответ в своей ошибке, которая является ссылкой на вашу ошибку WebKit? .. как я могу воспроизвести ту же ошибку с FireFox: /   -  person fguillen    schedule 21.08.2012
comment
@fguillen: это в комментариях к опубликованному ответу (который, кстати, имеет обходной путь для Firefox).   -  person pimvdb    schedule 21.08.2012
comment
@pimvdb Почему вы не принимаете ответ H.D.?   -  person TylerH    schedule 20.09.2017
comment
Отсутствие участия CSS в этом обходном пути кажется более простым. Сделали это с помощью событий указателя, и сейчас мне больше нравится вариант счетчика.   -  person brittongr    schedule 02.10.2017
comment
Я обнаружил, что самым простым решением этой действительно досадной проблемы является прослушивание только enter события на элементе, а при возникновении события - создание абсолютно позиционированного оверлея над данным элементом с только leave прослушивателем событий. Это устраняет необходимость отключения событий указателя на дочерних элементах (наложение берет на себя каждое событие перетаскивания), и вы уверены, что leave будет запущен, когда он должен быть. У меня была эта проблема в компоненте vue: всегда запускал leave сразу после enter, не понимал, почему на самом деле (у детей события указателя были установлены на none).   -  person Przemysław Melnarowicz    schedule 09.03.2020


Ответы (36)


Вам просто нужно сохранить счетчик ссылок, увеличивать его, когда вы получаете перетаскивание, и уменьшать, когда вы получаете перетаскивание. Когда счетчик на 0 - удалите класс.

var counter = 0;

$('#drop').bind({
    dragenter: function(ev) {
        ev.preventDefault(); // needed for IE
        counter++;
        $(this).addClass('red');
    },

    dragleave: function() {
        counter--;
        if (counter === 0) { 
            $(this).removeClass('red');
        }
    }
});

Примечание. В случае отбрасывания сбросьте счетчик на ноль и очистите добавленный класс.

Вы можете запустить его здесь

person Woody    schedule 08.01.2014
comment
OMG, это наиболее очевидное решение, и у него был только ОДИН голос ... Да ладно, люди, вы можете сделать лучше. Я думал об этом, но, увидев уровень сложности первых нескольких ответов, почти отказался от этого. Были ли у вас недостатки? - person Arthur Corenzan; 15.07.2014
comment
Привет, @ArthurCorenzan - спасибо. Нет - работает нормально, на самом деле я придумал еще более простой способ, показанный выше. - person Woody; 20.08.2014
comment
О нет, вы просто сорвали мой первый комментарий: P Мне больше понравилось предыдущее решение (предотвращение всплытия события dragleave дочерних элементов). - person Arthur Corenzan; 21.08.2014
comment
Первое решение в целом не сработало, например. если у вас были вложенные дочерние элементы, так и будет, и меньше кода, победа :) - person Woody; 21.08.2014
comment
Оно работает!! Намного лучше, чем принятый ответ. Так просто! : ') - person kornfridge; 21.10.2014
comment
Воспользуйтесь этой полезной библиотекой для перетаскивания HTML5: github.com/stevendwood/html5-dropzone - person Woody; 16.11.2014
comment
Это не сработало, когда край перетаскиваемого элемента касается края другого перетаскиваемого элемента. Например, сортируемый список; перетаскивание элемента вниз к следующему перетаскиваемому элементу не уменьшает счетчик обратно до 0, вместо этого он застревает на 1. Но если я перетащу его в сторону из любого другого перетаскиваемого элемента, это сработает. Я пошел с pointer-events: none на детей. Когда я начинаю перетаскивать, я добавляю класс с этим свойством, когда перетаскиваю, я удаляю класс. Хорошо работает в Safari и Chrome, но не в Firefox. - person Arthur Corenzan; 14.03.2015
comment
Это сработало для меня с Firefox 37 и Chromium 39, но не с IE11 (событий dragenter больше, чем событий dragleave). Собственно, даже скрипка с IE11 не работает. - person Damien; 14.04.2015
comment
Добавление e.preventDefault () в dragenter (e), похоже, исправило это для IE11. Когда я читал это, у меня возникла идея: Катастрофа перетаскивания HTML5 - person Damien; 14.04.2015
comment
Обновил ссылку. Причина, по которой он не работает в IE, заключается в том, что тип содержимого text / plain в начале перетаскивания, когда он исправлен, он отказывается запускать любые события dragleave, если вы не отмените событие dragenter ... еще одна причуда - person Woody; 14.04.2015
comment
@LucaFagioli - в каком смысле это взлом и почему это может быть опасно? - person Woody; 26.04.2015
comment
@Woody Это хитрость, потому что для работы в IE нужно добавить особое условие. Более того, вам нужна глобальная переменная для каждого селектора, для которого вы хотите запустить событие dragenter, которое испортит ваш код. Наконец, это опасно, потому что, если по какой-то причине вам нужно вызвать stopPropagation () для дочернего элемента, это решение сломается. - person Luca Fagioli; 26.04.2015
comment
Прежде всего, если вы думаете, что у вас есть лучший ответ на опубликованную проблему, тогда напишите ответ, а не комментарий к ответу, получившему наибольшее количество голосов, пытаясь рассказать всем, насколько вы умны и насколько глупый ответ, и всем, кто считает его лучшим. ответ есть. Во-вторых, ваша скрипка, очевидно, не работает, поскольку она использует только один счетчик для отслеживания событий на двух элементах, я имею в виду, как вы ожидаете, что код в ответе будет работать в этом случае !? Очевидно, что вам нужно отслеживать события для каждого элемента, который вы хотите прослушивать, что и задают вопросы. - person Woody; 26.04.2015
comment
не работает при выезде слева (когда нет запаса) - person caub; 18.11.2015
comment
У меня это сработало во всех браузерах, кроме Firefox. У меня есть новое решение, которое в моем случае работает везде. На первом dragenter сохраняю event.currentTarget в новую переменную dragEnterTarget. Пока установлен dragEnterTarget, я игнорирую другие события dragenter, потому что они исходят от детей. Во всех dragleave событиях я проверяю dragEnterTarget === event.target. Если это ложь, событие будет проигнорировано, так как оно было запущено дочерним элементом. Если это правда, я сбрасываю dragEnterTarget на undefined. - person Pipo; 06.01.2016
comment
Отличное решение. Мне нужно было сбросить счетчик на 0 в функции, которая обрабатывает прием сброса, иначе последующие перетаскивания не работали должным образом. - person Liam Johnston; 07.01.2016
comment
Обратите внимание, что это не работает, если какой-либо дочерний элемент прекращает распространение событий. Вот мое решение с document.elementFromPoint, которое еще проще: stackoverflow.com/a/35117158/1620264 (я не знаю ' t действительно использовал elementFromPoint раньше, поэтому не уверен на 100%, что мое решение правильное) - person marcelj; 31.01.2016
comment
Это не сработает, если вы уже что-то уронили. Счетчики будут рассинхронизированы - вам нужен способ определить, является ли это новым перетаскиванием или продолжением старого перетаскивания. - person B T; 18.07.2016
comment
@BT не помог ли сброс Лиама Джонстона в функции сброса? Если нет, вы можете попробовать добавить прослушиватель dragend, который сбрасывает счетчик. Я думаю, что драгенд должен стрелять в тащимую вещь, но событие должно распространяться так, чтобы вы могли поймать его на теле. - person Woody; 18.07.2016
comment
@Woody Я не видел его комментария в огромном списке комментариев здесь, но да, это исправление. Почему бы не включить это в свой ответ? - person B T; 19.07.2016
comment
Для поддержки Firefox я также добавил прослушиватель ondragexit для уменьшения счетчика, если не 0. - person BobTheBuilder; 21.08.2017
comment
@BobTheBuilder, @B T не могли бы вы предложить правку, и я добавлю ее к примеру? - person Woody; 29.08.2017
comment
Чтобы это работало надежно, мне пришлось (а) хранить счетчик для каждого элемента, а не глобально, и (б) отказаться от старых счетчиков, когда началось новое перетаскивание. Для этого я использовал глобальный счетчик генерации var dragRefCountGen = 0;, увеличивал его в dragstart: dragRefCountGen++; и сбрасывал счетчик в dragenter, когда мы находимся в новом перетаскивании: if (this.dragRefCountGen != dragRefCountGen) { this.dragRefCount = 0; this.dragRefCountGen = dragRefCountGen; }. Оттуда, как и раньше - увеличивайте this.dragRefCount в перетаскивании, уменьшайте и проверяйте this.dragRefCount в перетаскивании. - person Josh Bleecher Snyder; 30.01.2018
comment
@Woody рад предложить это в качестве исправления, если хотите. - person Josh Bleecher Snyder; 30.01.2018
comment
@JoshBleecherSnyder, не могли бы вы опубликовать полный код в качестве ответа, пожалуйста - person Chin; 12.04.2019
comment
Это решение не очень надежно, потому что, когда вы быстро перетаскиваете объекты, не все элементы в дереве на самом деле запускают событие, что может привести к сумме, отличной от 0, после перетаскивания. Это происходит чаще, если у вас есть много других элементов в дереве dom между самым внешним и самым внутренним элементом. - person Matt Leonowicz; 16.05.2019
comment
@MattLeonowicz Я пробовал это решение с очень сложными элементами, и ваше беспокойство не проявилось. - person Peter Moore; 28.09.2019
comment
@Pipo Что такое event.currentTarget, это вещь? Я вижу explicitOriginalTarget, originalTarget, relatedTarget, но не currentTarget, по крайней мере, в Firefox. - person Bersan; 22.01.2020
comment
Этот ответ похож на волшебство! Просто работает. Использовал этот метод для DropZone и HTML-таблицы для выделения строк (Dropzones) для загрузки файлов. - person tlorens; 17.11.2020

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

да.

#drop * {pointer-events: none;}

Кажется, этого CSS достаточно для Chrome.

При использовании с Firefox у #drop не должно быть текстовых узлов напрямую (иначе есть странный проблема, когда элемент" оставил его самому себе "), поэтому я предлагаю оставить его только с одним элементом (например, используйте div внутри #drop, чтобы поместить все внутри)

Вот jsfiddle, решающий пример исходного (неработающего) вопроса.

Я также сделал упрощенную версию, разветвленную из примера @Theodore Brown, но основанную только на этом CSS. .

Однако не во всех браузерах реализован этот CSS: http://caniuse.com/pointer-events

Увидев исходный код Facebook, я мог найти это pointer-events: none; несколько раз, однако он, вероятно, использовался вместе с изящными откатами для деградации. По крайней мере, это так просто и решает проблему для многих сред.

person H.D.    schedule 03.09.2013
comment
Свойство pointer-events - правильное решение в будущем, но, к сожалению, оно не работает в IE8-IE10, которые до сих пор широко используются. Кроме того, я должен указать, что ваш текущий jsFiddle даже не работает в IE11, поскольку он не добавляет необходимых прослушивателей событий и предотвращения поведения по умолчанию. - person Theodore Brown; 19.09.2013
comment
Использование событий-указателей - действительно хороший ответ, я немного боролся, прежде чем сам понял, что ответ должен быть выше. - person floribon; 14.08.2014
comment
Отличный вариант для тех из нас, кому посчастливилось не поддерживать старые браузеры. - person Luke Hansford; 05.10.2015
comment
Что делать, если ваши дети - пуговицы? o.O - person David; 15.03.2016
comment
Отлично работает - и вы все еще можете добавить счетчик для старых браузеров. Спасибо! - person BurninLeo; 28.05.2016
comment
это лучшее решение на данный момент, просто назначьте идентификатор родительскому элементу (например, parent_element) и в файле css #parent_element * {pointer-events: none;} - person Alan Deep; 12.05.2018
comment
он работает, только если у вас нет других элементов контроллера (редактировать, удалять) внутри области, потому что это решение также блокирует их .. - person Zoltán Süle; 09.10.2018
comment
Это должен быть принятый ответ. Или ответьте вверху. - person Konstantin Kozirev; 31.03.2020
comment
Для интерактивных дочерних элементов внутри целевого объекта перетаскивания, например кнопки: Добавить pointer-events: none; в класс. Примените класс к цели перетаскивания с помощью ondragenter. Затем удалите класс из цели перетаскивания в ondragleave. - person sudoqux; 02.06.2020
comment
@sudoqux Вы имеете в виду применить задачу к дочерним элементам цели перетаскивания, используя .droptarget * - person sw1337; 21.09.2020
comment
Но я хочу, чтобы мои дочерние элементы оставались интерактивными :( - person Robo Robok; 12.06.2021

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

Мне удалось исправить ту же проблему, что и недавно, благодаря ответу в этом answer и подумал, что это может быть полезно для тех, кто заходит на эту страницу. Вся идея состоит в том, чтобы хранить evenet.target в ondrageenter каждый раз, когда он вызывается в любом из родительских или дочерних элементов. Затем в ondragleave проверьте, совпадает ли текущая цель (event.target) с объектом, который вы сохранили в ondragenter.

Единственный случай, когда эти два совпадают, - это когда ваш перетаскивание покидает окно браузера.

Причина, по которой это работает нормально, заключается в том, что когда мышь покидает элемент (скажем, el1) и входит в другой элемент (скажем, el2), сначала вызывается el2.ondragenter, а затем el1.ondragleave. Только когда перетаскивание выходит / входит в окно браузера, event.target будет '' как в el2.ondragenter, так и в el1.ondragleave.

Вот мой рабочий образец. Я тестировал его в IE9 +, Chrome, Firefox и Safari.

(function() {
    var bodyEl = document.body;
    var flupDiv = document.getElementById('file-drop-area');

    flupDiv.onclick = function(event){
        console.log('HEy! some one clicked me!');
    };

    var enterTarget = null;

    document.ondragenter = function(event) {
        console.log('on drag enter: ' + event.target.id);
        enterTarget = event.target;
        event.stopPropagation();
        event.preventDefault();
        flupDiv.className = 'flup-drag-on-top';
        return false;
    };

    document.ondragleave = function(event) {
        console.log('on drag leave: currentTarget: ' + event.target.id + ', old target: ' + enterTarget.id);
        //Only if the two target are equal it means the drag has left the window
        if (enterTarget == event.target){
            event.stopPropagation();
            event.preventDefault();
            flupDiv.className = 'flup-no-drag';         
        }
    };
    document.ondrop = function(event) {
        console.log('on drop: ' + event.target.id);
        event.stopPropagation();
        event.preventDefault();
        flupDiv.className = 'flup-no-drag';
        return false;
    };
})();

А вот простая html-страница:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Multiple File Uploader</title>
<link rel="stylesheet" href="my.css" />
</head>
<body id="bodyDiv">
    <div id="cntnr" class="flup-container">
        <div id="file-drop-area" class="flup-no-drag">blah blah</div>
    </div>
    <script src="my.js"></script>
</body>
</html>

При правильном оформлении я сделал внутренний div (# file-drop-area) намного больше всякий раз, когда файл перетаскивается на экран, чтобы пользователь мог легко перетащить файлы в нужное место.

person Ali Motevallian    schedule 20.10.2014
comment
Это лучшее решение, оно лучше, чем счетчик (особенно если вы делегируете события), и оно также работает с перетаскиваемыми дочерними элементами. - person Fred; 12.10.2015
comment
Это не помогает решить проблему дублирования событий перетаскивания. - person B T; 18.07.2016
comment
Работает ли это для детей, которые являются псевдоэлементами или псевдоклассами css? Я не мог этого понять, но, возможно, я делал это неправильно. - person Josh Bleecher Snyder; 30.01.2018
comment
По состоянию на март 2020 года мне тоже очень повезло с этим решением. Примечание: некоторые линтеры могут жаловаться на использование == вместо ===. Вы сравниваете ссылки на целевые объекты событий, поэтому === тоже работает нормально. - person Alex; 24.03.2020

Вот простейшее кроссбраузерное решение (серьезно):

jsfiddle ‹- попробуйте перетащить какой-нибудь файл внутрь окна

Вы можете сделать что-то подобное:

var dropZone= document.getElementById('box');
var dropMask = document.getElementById('drop-mask');

dropZone.addEventListener('dragover', drag_over, false);
dropMask.addEventListener('dragleave', drag_leave, false);
dropMask.addEventListener('drop', drag_drop, false);

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

После ухода или падения вы просто снова скрываете маску.
Просто и без сложностей.

(Прим .: совет Грега Петтита - вы должны быть уверены, что маска парит над всем прямоугольником, включая границу)

person Diego T. Yamaguchi    schedule 06.05.2013
comment
Не уверен, почему, но он не работает должным образом с Chrome. Иногда при выходе из этой области маска остается видимой. - person Greg Pettit; 23.05.2013
comment
Собственно, это граница. Сделайте так, чтобы маска перекрывала границу без собственной границы, и она должна работать нормально. - person Greg Pettit; 23.05.2013
comment
Обратите внимание, в вашем jsfiddle есть ошибка, в drag_drop вы должны удалить класс наведения на #box, а не на # box-a - person entropy; 16.12.2013
comment
Это хорошее решение, но у меня как-то не сработало. Я наконец нашел обходной путь. Для тех из вас, кто ищет что-то еще. вы можете попробовать это: github.com/bingjie2680/jquery-draghover - person bingjie2680; 19.06.2014
comment
Нет. Я не хочу терять контроль над дочерними элементами. Я сделал простой плагин jQuery, который решает проблему, выполняя эту работу за нас. Проверить мой ответ. - person Luca Fagioli; 25.04.2015
comment
Это решение отлично работает, когда фактическая маска - #drop::before или ::after. Также имейте в виду, что иногда при быстром перетаскивании драглайв срабатывает до того, как перетащите центр. Если центр перетаскивания добавляет класс / псевдоэлемент, а затем перетаскивание удаляет, это может вызвать проблему. - person le hollandais volant; 03.12.2018
comment
Следуя комментарию le hollandais volant, у меня все еще возникали проблемы с перепрошивкой из-за dropave и dropenter даже с событиями Pointer: нет. Таким образом, я в основном устанавливаю тайм-аут на дроплэйв, который очищается дропентером. Сортировано. - person Tod; 21.09.2020

«Правильный» способ решить эту проблему - отключить события указателя на дочерних элементах цели перетаскивания (как в ответе @HD). Вот созданный мной jsFiddle, демонстрирующий эту технику. К сожалению, это не работает в версиях Internet Explorer до IE11, поскольку они не поддерживают события указателя.

К счастью, мне удалось найти обходной путь, который действительно работает в старых версиях IE. По сути, это включает в себя идентификацию и игнорирование dragleave событий, которые происходят при перетаскивании дочерних элементов. Поскольку событие dragenter запускается на дочерних узлах перед событием dragleave на родительском, к каждому дочернему узлу могут быть добавлены отдельные прослушиватели событий, которые добавляют или удаляют класс «игнорировать-перетаскивать-оставлять» из цели перетаскивания. Тогда прослушиватель событий dragleave цели перетаскивания может просто игнорировать вызовы, которые происходят, когда этот класс существует. Вот jsFiddle, демонстрирующий этот обходной путь. Он протестирован и работает в Chrome, Firefox и IE8 +.

Обновление:

Я создал jsFiddle, демонстрирующий комбинированное решение с использованием функции обнаружения, где используются события указателя, если они поддерживаются (в настоящее время Chrome, Firefox, и IE11), и браузер возвращается к добавлению событий в дочерние узлы, если поддержка событий указателя недоступна (IE8-10).

person Theodore Brown    schedule 30.08.2013
comment
Этот ответ показывает обходной путь для нежелательного срабатывания, но игнорирует вопрос. Можно ли предотвратить срабатывание dragleave при перетаскивании в дочерний элемент? полностью. - person H.D.; 03.09.2013
comment
Поведение может стать странным при перетаскивании файла из-за пределов браузера. В Firefox у меня есть Входящий дочерний элемент - ›Входящий родительский элемент -› Уходящий дочерний элемент - ›Входящий дочерний элемент -› Уходящий дочерний элемент, не покидая родителя, который ушел с более высоким классом. Старому IE потребуется замена attachEvent для addEventListener. - person H.D.; 03.09.2013
comment
Это решение сильно зависит от всплытия, ложное значение во всех addEventListener должно быть подчеркнуто как важное (хотя это поведение по умолчанию), поскольку многие люди могут не знать об этом. - person H.D.; 03.09.2013
comment
Этот единственный перетаскиваемый объект добавляет эффект к области перетаскивания, который не появляется, когда область перетаскивания используется для других перетаскиваемых объектов, которые не запускают событие перетаскивания. Возможно, использование всего в качестве зоны перетаскивания для эффекта перетаскивания при сохранении реальной зоны перетаскивания с другими обработчиками сделало бы это. - person H.D.; 03.09.2013
comment
@ H.D. Я обновил свой ответ информацией об использовании свойства CSS pointer-events, чтобы предотвратить запуск события dragleave в Chrome, Firefox и IE11 +. Я также обновил свой другой обходной путь для поддержки IE8 и IE9 в дополнение к IE10. Эффект dropzone добавляется только при перетаскивании ссылки «Перетащить меня». Другие могут свободно изменять это поведение по мере необходимости для поддержки своих вариантов использования. - person Theodore Brown; 19.09.2013
comment
Это предотвратит запуск события щелчка дочерними элементами. - person boblapointe; 06.05.2014

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

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}
person Kenneth Spencer    schedule 19.01.2019
comment
Отлично! спасибо, что поделились @kenneth, ты спас мне день! Многие другие ответы применимы только в том случае, если у вас есть одна зона отбрасывания. - person antoni; 18.02.2020
comment
Для меня это работает в Chrome и Firefox, но id не работает в Edge. См .: jsfiddle.net/iwiss/t9pv24jo - person iwis; 25.03.2020

если вы используете HTML5, вы можете получить родительский clientRect:

let rect = document.getElementById("drag").getBoundingClientRect();

Затем в parent.dragleave ():

dragleave(e) {
    if(e.clientY < rect.top || e.clientY >= rect.bottom || e.clientX < rect.left || e.clientX >= rect.right) {
        //real leave
    }
}

вот jsfiddle

person azlar    schedule 10.03.2017
comment
Отличный ответ. - person broc.seib; 31.01.2018
comment
Спасибо, приятель, мне нравится простой подход javascript. - person Tim Gerhard; 26.06.2019
comment
При использовании элементов, имеющих border-radius, перемещение указателя ближе к углу фактически оставит элемент, но этот код все равно будет думать, что мы внутри (мы оставили элемент, но все еще находимся в ограничивающем прямоугольнике ). Тогда обработчик события dragleave вообще не будет вызываться. - person Jay Dadhania; 13.12.2019

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

Я пробовал различные методы проверки, совпадает ли элемент e.target с элементом this, но не смог добиться каких-либо улучшений.

То, как я решил эту проблему, было чем-то вроде взлома, но оно работает на 100%.

dragleave: function(e) {
               // Get the location on screen of the element.
               var rect = this.getBoundingClientRect();

               // Check the mouseEvent coordinates are outside of the rectangle
               if(e.x > rect.left + rect.width || e.x < rect.left
               || e.y > rect.top + rect.height || e.y < rect.top) {
                   $(this).removeClass('red');
               }
           }
person Greg    schedule 18.07.2012
comment
Спасибо! Однако я не могу заставить это работать в Chrome. Не могли бы вы предоставить рабочую скрипку своего хака? - person pimvdb; 18.07.2012
comment
Я тоже думал сделать это, проверив координаты. Вы сделали за меня большую часть работы, спасибо :). Однако мне пришлось внести некоторые коррективы: if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top) - person Christof; 04.07.2013
comment
Это не будет работать в Chrome, потому что это событие не имеет e.x и e.y. - person Hengjie; 01.09.2013
comment
Мне нравится это решение. Сначала не работал в Firefox. Но если вы замените e.x на e.clientX и e.y на e.clientY, это сработает. Также работает в Chrome. - person Nicole Stutz; 03.11.2013
comment
У меня не работает в Chrome, и то, что Крис или Дэниел Статс предложил - person Timo Huovinen; 14.03.2014
comment
В chrome используйте e.originalEvent.x и e.originalEvent.y вместо e.x и e.y. - person stonea; 02.07.2015
comment
Хорошее решение, но оно не работает, если e.currentTarget контуры не прямоугольные. - person Max; 02.05.2019
comment
Я использую еще более простую модификацию кода @Christof: if (e.x <= rect.left || e.x >= rect.right || e.y <= rect.top || e.y >= rect.bottom) - person iwis; 25.03.2020

Очень простое решение - использовать свойство pointer-events CSS. Просто установите для него значение none при перетаскивании для каждого дочернего элемента. Эти элементы больше не будут запускать события, связанные с мышью, поэтому они не будут ловить указатель мыши на них и, следовательно, не будут запускать dragleave для родительского элемента.

Не забудьте вернуть для этого свойства значение auto после завершения перетаскивания;)

person FruityFred    schedule 25.04.2017

Простое решение - добавить правило 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>

person Alexandre Annic    schedule 04.07.2018
comment
У меня была такая же проблема, и ваше решение отлично работает. Совет для других, это был мой случай: если дочерний элемент, который вы пытаетесь заставить игнорировать событие drag, является клоном перетаскиваемого элемента (пытаясь добиться визуального предварительного просмотра), вы можете использовать это внутри dragstart при создании клона: dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none'; - person Scaramouche; 02.11.2018
comment
Красивое простое решение. Люди могут захотеть просто создать класс, .no-pointer-events {pointer-events: none;} затем добавить no-pointer-events к каждому дочернему элементу. Выполнено. - person Rob; 14.11.2020
comment
Это должен быть принятый ответ, он самый простой и не требует взлома. : D - person Ambrus Tóth; 20.04.2021

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

parent.addEventListener('dragleave', function(evt) {
    if (!parent.contains(evt.relatedTarget)) {
        // Here it is only dragleave on the parent
    }
}
person Owen M    schedule 02.03.2019
comment
Похоже, это не работает в Safari 12.1: jsfiddle.net/6d0qc87m - person Adam Taylor; 30.04.2019

Вы можете исправить это в Firefox, вдохновившись кодом jQuery.:

dragleave: function(e) {
    var related = e.relatedTarget,
        inside = false;

    if (related !== this) {

        if (related) {
            inside = jQuery.contains(this, related);
        }

        if (!inside) {

            $(this).removeClass('red');
        }
    }

}

К сожалению, это не работает в Chrome, потому что relatedTarget не существует для dragleave событий , и я предполагаю, что вы работаете в Chrome, потому что ваш пример не работал в Firefox. Вот версия с реализованным выше кодом.

person robertc    schedule 19.08.2011
comment
Большое спасибо, но я действительно пытаюсь решить эту проблему в Chrome. - person pimvdb; 19.08.2011
comment
@pimvdb Я вижу, вы зарегистрировали ошибку, я просто оставлю ссылка на него здесь, если кто-то еще встретит этот ответ. - person robertc; 19.08.2011
comment
Я действительно это сделал, но я забыл добавить сюда ссылку. Спасибо за это. - person pimvdb; 19.08.2011
comment
Тем временем ошибка Chrome была исправлена. - person phk; 14.07.2017

И вот оно, решение для Chrome:

.bind('dragleave', function(event) {
                    var rect = this.getBoundingClientRect();
                    var getXY = function getCursorPosition(event) {
                        var x, y;

                        if (typeof event.clientX === 'undefined') {
                            // try touch screen
                            x = event.pageX + document.documentElement.scrollLeft;
                            y = event.pageY + document.documentElement.scrollTop;
                        } else {
                            x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
                            y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
                        }

                        return { x: x, y : y };
                    };

                    var e = getXY(event.originalEvent);

                    // Check the mouseEvent coordinates are outside of the rectangle
                    if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) {
                        console.log('Drag is really out of area!');
                    }
                })
person Community    schedule 12.10.2012
comment
Переходит ли он в «if (typeof event.clientX === 'undefined')»? - person Aldekein; 17.12.2012
comment
Работает хорошо, но над браузером может быть еще одно окно, поэтому получить местоположение мыши и сравнить его с прямоугольной областью экрана недостаточно. - person H.D.; 14.08.2013
comment
Я согласен с @ H.D. Кроме того, это вызовет проблемы, если элемент имеет большой border-radius, как я объяснил в своем комментарии к ответу @azlar выше. - person Jay Dadhania; 13.12.2019

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

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

person marcelj    schedule 31.01.2016
comment
Из коробки у меня это не сработало. Вместо этого мне нужно было использовать event.clientX, event.clientY, поскольку они относятся к области просмотра, а не к странице. Я надеюсь, что это поможет другой заблудшей душе. - person Jon Abrams; 08.06.2016

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

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

У меня есть 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;
});
person chrisallick    schedule 18.09.2012
comment
Хакерский, но умный. Мне нравится. Работал у меня. - person cupcakekid; 09.06.2015
comment
О, как бы мне хотелось, чтобы у этого ответа было больше голосов! Спасибо - person Kirby; 11.09.2015

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

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

person Marcin Raczkowski    schedule 11.06.2013
comment
Единственное, что делает этот фрагмент, - предотвращает удаление класса «зависшего», повторно применяя его в следующем цикле тиков (что делает событие «dragleave» бесполезным). - person null; 12.10.2013
comment
Это не бесполезно. Если вы оставите родителей, все будет работать, как ожидалось. Сила этого решения в его простоте, оно не идеальное или лучшее, что есть на свете. Лучшим решением было бы отметить вход для дочернего элемента при проверке onleave, если мы только что ввели дочерний элемент, и если это так, не запускать событие выхода. Он будет нуждаться в тестировании, дополнительной охране, проверке внуков и т. Д. - person Marcin Raczkowski; 15.10.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) {
person Profet    schedule 14.07.2013
comment
Это не всегда работает в Chrome. Иногда я получаю clientX выше 0, когда мышь находится вне коробки. Конечно, мои элементы position:absolute - person Hengjie; 01.09.2013
comment
Это происходит постоянно или только иногда? Потому что, если мышь движется слишком быстро (например, за пределами окна), вы можете получить неверные значения. - person Profet; 02.09.2013
comment
Это случается в 90% случаев. В редких случаях (1 из 10) я могу довести его до нуля. Я попробую еще раз переместить мышь медленнее, но не могу сказать, что двигался быстро (возможно, это то, что вы бы назвали нормальной скоростью). - person Hengjie; 02.09.2013
comment
Используя dropzone.js, у меня сработал похожий подход: console.log(e.clientX + "/" + e.clientY ); if (e.clientX == 0 && e.clientY == 0 ) { console.log('REAL leave'); } - person leosok; 11.11.2020

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

person Ben    schedule 14.11.2013
comment
Очень полезно (по крайней мере, для меня, где меня интересует только Chrome). - person M Katz; 01.07.2015

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

Надеюсь, это поможет любому, у кого такая же проблема.

person rasmusx    schedule 18.02.2014
comment
Иногда не работает на хроме (не ловит драглейв должным образом) - person Timo Huovinen; 14.03.2014

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

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })
person sofarsoghood    schedule 09.06.2017
comment
Похоже на самое простое решение для меня, и оно решило мою проблему. Спасибо! - person Francesco Marchetti-Stasi; 01.02.2018

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

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
    }
});
person B T    schedule 17.07.2016

Я нашел простое решение этой проблемы, поэтому поделился им. В моем случае это работает хорошо.

jsfiddle попробуйте.

Фактически вы можете добиться этого только с помощью события dragenter, и вам даже не нужно регистрировать dragleave. Все, что вам нужно, - это иметь зону, закрывающую ваши дропзоны, и все.

У вас также могут быть вложенные dropzones, и это отлично работает. Также проверьте это вложенными зонами сброса.

$('.dropzone').on("dragenter", function(e) {
  e.preventDefault();
  e.stopPropagation();
  $(this).addClass("over");
  $(".over").not(this).removeClass("over"); // in case of multiple dropzones
});

$('.dropzone-leave').on("dragenter", function(e) {
  e.preventDefault();
  e.stopPropagation();
  $(".over").removeClass("over");
});

// UPDATE
// As mar10 pointed out, the "Esc" key needs to be managed,
// the easiest approach is to detect the key and clean things up.

$(document).on('keyup', function(e){
  if (e.key === "Escape") {
    $(".over").removeClass("over");
  }
});
person Abubakar Azeem    schedule 09.11.2020
comment
Я пробовал это решение, но следует отметить, что у него есть ограничение: при нажатии Esc для отмены действия перетаскивания никто не удалит класс over из вашего элемента dropzone. Если вы пытаетесь использовать для этого событие dragleave, вы просто переходите к первой проблеме, о которой спрашивал первоначальный автор. Таким образом, как наведение курсора на дочерний элемент, так и нажатие клавиши Esc при перетаскивании вызовет событие перетаскивания в зоне перетаскивания. Возможно, нам также нужно прослушать клавишу Esc, чтобы удалить класс over из dropzone ... - person mar10; 20.07.2021
comment
привет @ mar10, спасибо, что указали на эту проблему, я обновлю свой ответ. Я думаю, что мы можем использовать событие dragend, чтобы справиться с этим, но мне нужно будет это проверить. - person Abubakar Azeem; 22.07.2021
comment
Я не нашел способа определить, запускается ли событие dragend путем отпускания кнопки мыши или нажатия клавиши Esc, поэтому переход с событием dragend усложнит логику, поэтому простое решение - обнаружить клавишу Escape, как вы сказали . Я обновил ответ. - person Abubakar Azeem; 22.07.2021

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

У меня была аналогичная проблема - мой код для скрытия dropzone при событии dragleave для тела запускался постоянно при наведении курсора на дочерние элементы, из-за чего 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);
person Arseny    schedule 01.12.2014
comment
Я тоже этим занимаюсь, но это все еще отрывочно. - person mpen; 29.01.2016

Событие «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");
});

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

person Lalit Nankani    schedule 16.10.2018

Решено ..!

Объявите любой массив, например:

targetCollection : any[] 

dragenter: function(e) {
    this.targetCollection.push(e.target); // For each dragEnter we are adding the target to targetCollection 
    $(this).addClass('red');
},

dragleave: function() {
    this.targetCollection.pop(); // For every dragLeave we will pop the previous target from targetCollection
    if(this.targetCollection.length == 0) // When the collection will get empty we will remove class red
    $(this).removeClass('red');
}

Не нужно беспокоиться о дочерних элементах.

person Shubham Rajodiya    schedule 17.09.2019

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

В моем примере ниже у меня есть таблица, в которой я хочу иметь возможность обмениваться содержимым строк таблицы друг с другом с помощью API перетаскивания. На dragenter класс CSS должен быть добавлен к элементу строки, в которую вы в данный момент перетаскиваете свой элемент, чтобы выделить его, а на dragleave этот класс должен быть удален.

Пример:

Очень простая таблица HTML:

<table>
  <tr>
    <td draggable="true" class="table-cell">Hello</td>
  </tr>
  <tr>
    <td draggable="true" clas="table-cell">There</td>
  </tr>
</table>

И функция обработчика событий dragenter, добавленная в каждую ячейку таблицы (кроме обработчиков dragstart, dragover, drop и dragend, которые не относятся к этому вопросу, поэтому здесь не копируются):

/*##############################################################################
##                              Dragenter Handler                             ##
##############################################################################*/

// When dragging over the text node of a table cell (the text in a table cell),
// while previously being over the table cell element, the dragleave event gets
// fired, which stops the highlighting of the currently dragged cell. To avoid
// this problem and any coding around to fight it, everything has been
// programmed with the dragenter event handler only; no more dragleave needed

// For the dragenter event, e.target corresponds to the element into which the
// drag enters. This fact has been used to program the code as follows:

var previousRow = null;

function handleDragEnter(e) {
  // Assure that dragenter code is only executed when entering an element (and
  // for example not when entering a text node)
  if (e.target.nodeType === 1) {
    // Get the currently entered row
    let currentRow = this.closest('tr');
    // Check if the currently entered row is different from the row entered via
    // the last drag
    if (previousRow !== null) {
      if (currentRow !== previousRow) {
        // If so, remove the class responsible for highlighting it via CSS from
        // it
        previousRow.className = "";
      }
    }
    // Each time an HTML element is entered, add the class responsible for
    // highlighting it via CSS onto its containing row (or onto itself, if row)
    currentRow.className = "ready-for-drop";
    // To know which row has been the last one entered when this function will
    // be called again, assign the previousRow variable of the global scope onto
    // the currentRow from this function run
    previousRow = currentRow;
  }
}

В коде оставлены очень простые комментарии, так что этот код подходит и для новичков. Надеюсь, это поможет вам! Обратите внимание, что вам, конечно, нужно будет добавить все прослушиватели событий, о которых я упоминал выше, в каждую ячейку таблицы, чтобы это работало.

person DevelJoe    schedule 02.06.2020

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

Событие 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
    }
})
person smrtl    schedule 01.05.2013

pimvdb ..

Почему бы вам не попробовать использовать drop вместо dragleave. У меня это сработало. надеюсь, это решит вашу проблему.

Проверьте jsFiddle: http://jsfiddle.net/HU6Mk/118/

$('#drop').bind({
                 dragenter: function() {
                     $(this).addClass('red');
                 },

                 drop: function() {
                     $(this).removeClass('red');
                 }
                });

$('#drag').bind({
                 dragstart: function(e) {
                     e.allowedEffect = "copy";
                     e.setData("text/plain", "test");
                 }
                });
person abhi    schedule 03.01.2014
comment
Если пользователь передумает и перетащит файл обратно из браузера, он останется красным. - person ZachB; 03.05.2015

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

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

person ilovett    schedule 23.07.2014

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

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) {
                //
            }
        }
    }
person EddieB    schedule 10.06.2015

используйте этот код 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');
             }
        }
    });
person Mohammad Rezaei    schedule 20.06.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');

  }
})

Гвидо

person GNaruhn    schedule 05.07.2016

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

person zysite    schedule 13.02.2018

Вот мое решение (https://jsfiddle.net/42mh0fd5/8):

<div id="droppable">
    <div id="overlay"></div>
    <a href="">test child 1</a>
    <br /><br />
    <button>test child 2</button>
    <br /><br />
    <button>test child 3</button>
    <br />
</div>
<p id="draggable" draggable="true">This element is draggable.</p>


<script type="text/javascript">
var dropElem = document.getElementById('droppable');
var overlayElem = document.getElementById('overlay');

overlayElem.addEventListener('drop', function(ev) {
    ev.preventDefault();
    console.log('drop', ev.dataTransfer.files)
    overlayElem.classList.remove('dragover')
    dropElem.classList.add('dropped')
    console.log('drop')
}, false);

overlayElem.addEventListener('dragover', function(ev) {
    ev.preventDefault();
}, false);

overlayElem.addEventListener('dragleave', function(ev) {
    console.log('dragleave')
    overlayElem.classList.remove('dragover')
}, false);

dropElem.addEventListener('dragenter', function(ev) {
    console.log('dragenter')
    overlayElem.classList.add('dragover')
}, false);
</script>

<style>
#draggable{
    padding:5px;
    background: #fec;
    display: inline-block;
}
#droppable{
    width: 300px;
    background: #eef;
    border: 1px solid #ccd;
    position: relative;
    text-align: center;
    padding:30px;
}
#droppable.dropped{
    background: #fee;
}
#overlay.dragover{
    content:"";
    position: absolute;
    z-index: 1;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background: rgba(0,0,0,.2);
    border:3px dashed #999;
}
</style>
person Pasquale Di Stasio    schedule 25.10.2019

Я смог сделать это, используя тайм-аут dragleave. В отличие от других ответов с этим подходом, я думаю, что очень важно сбросить этот тайм-аут на dragover, чтобы избежать мерцания.

Я пишу только те функции, которые вам нужны для привязки, используя выбранную вами структуру.

let dragoverTimeout;
const onDragOver = (e: Event) => {
  e.preventDefault();
  e.stopPropagation();

  if (dragoverTimeout) {
    window.clearTimeout(dragoverTimeout);
    dragoverTimeout = null;
  }
  // Add your class here
}

const onDragLeave = (e: Event) => {
  e.preventDefault();
  e.stopPropagation();

  if (!dragoverTimeout) {
    dragoverTimeout = window.setTimeout(() => {
      // Remove your class here
    }, 100);
  }
}

const onDrop = (e) => {
  e.preventDefault();
  e.stopPropagation();

  const files = e.dataTransfer.files;
  // Remove your class here

  if (files.length > 0) {
    this.uploadFile(files);
  }
}
person Martin Vich    schedule 10.06.2021