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 ]

292 голосов
/ 08 января 2014

Вам просто нужно сохранить счетчик ссылок, увеличивать его, когда вы получаете драгентер, уменьшать, когда вы получаете перетаскивание.Когда счетчик равен 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');
        }
    }
});

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

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

66 голосов
/ 03 сентября 2013

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

Да.

#drop * {pointer-events: none;}

Этого CSS достаточно для Chrome.

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

Вот jsfiddle , решающий оригинальный вопрос (сломанный) пример .

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

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

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

42 голосов
/ 06 мая 2013

Здесь самое простое кросс-браузерное решение (серьезно):

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

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

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

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

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

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

34 голосов
/ 31 августа 2013

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

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

Обновление:

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

26 голосов
/ 20 октября 2014

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

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

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

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

14 голосов
/ 18 июля 2012

Проблема в том, что событие 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');
               }
           }
9 голосов
/ 25 апреля 2017

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

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

8 голосов
/ 19 августа 2011

Вы можете исправить это в 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*Кажется, что 1008 * не существует в событиях dragleave, и я предполагаю, что вы работаете в Chrome, потому что ваш пример не работал в Firefox. Вот версия с реализованным выше кодом.

7 голосов
/ 10 марта 2017

если вы используете 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

7 голосов
/ 12 октября 2012

И вот, решение для 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!');
                    }
                })
...