Під час наведенні наведення дочірнього елемента запускається перетяжка HTML5


300

Проблема, яка у мене виникає, полягає в тому, що 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.


Помилка pimvdb, про яку повідомляється, все ще існує у Вебкіті станом на травень 2012 року. Я протидіяв їй, додавши також клас у драговері, який ніде не близький до приємного, оскільки він запускається так часто, але, здається, трохи виправить проблему.
айм

2
@ajm: Дякую, це працює певною мірою. Однак у Chrome є спалах при введенні або виходженні з дочірнього елемента, імовірно, тому що dragleaveвсе-таки спрацьовує.
pimvdb

Я відкрив оновлення помилок у користувальницькому інтерфейсі jQuery , тому вони можуть вирішити використати ресурси
fguillen

@fguillen: Вибачте, але це не має нічого спільного з інтерфейсом jQuery. Насправді, jQuery навіть не потрібен, щоб викликати помилку. Помилка WebKit вже зареєстрована, але оновлень наразі немає.
pimvdb

2
@pimvdb Чому ви не приймаєте відповідь HD?
TylerH

Відповіді:


337

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

Примітка. У випадку падіння скиньте лічильник до нуля та очистіть доданий клас.

Ви можете запустити його тут


8
OMG - це найочевидніше рішення, і він мав лише ОДИН голос ... Приходьте, люди, ви можете зробити краще. Я думав над цим, але побачивши рівень витонченості перших кількох відповідей, я майже відмовився від цього. Чи були у вас недоліки?
Артур Корензан

11
Це не спрацювало, коли край перетягнутого елемента торкається краю іншого елементу, що перетягується. Наприклад, список сортування; перетягуючи елемент вниз, над наступним елементом, що перетягується, не зменшується лічильник назад до 0, натомість він застрягає в 1. Але якщо я перетягну його набік, з будь-якого іншого елементу, який перетягується, він працює. Я пішов з pointer-events: noneдітьми. Коли я починаю перетягувати, додаю клас, який має це властивість, при перетягуванні я видаляю клас. Добре працював на Safari та Chrome, але не на Firefox.
Артур Корензан

13
Це працювало на мене у всіх браузерах, окрім Firefox. У мене є нове рішення, яке працює в усьому моєму випадку. На першому dragenterя зберігаю event.currentTargetв новій змінній dragEnterTarget. Поки dragEnterTargetвстановлено, я ігнорую подальші dragenterподії, бо вони від дітей. У всіх dragleaveподіях я перевіряю dragEnterTarget === event.target. Якщо це неправда, подія буде проігноровано, оскільки її запустила дитина. Якщо це вірно скинути dragEnterTargetз undefined.
Піпо

3
Прекрасне рішення. Мені потрібно було скинути лічильник на 0 у функції, яка обробляє прийняття краплі, хоча в іншому випадку наступні перетяжки не спрацювали так, як очікувалося.
Ліам Джонстон

2
@Woody Я не побачив його коментар у величезному списку коментарів тут, але так, це виправлення. Чому б не включити це у свою відповідь?
BT

93

Чи можна запобігти стрілянині, коли тягнеться в дочірній елемент?

Так.

#drop * {pointer-events: none;}

Цього CSS, здається, достатньо для Chrome.

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

Ось jsfiddle, що розв’язує оригінальний приклад запитання (ламаний) .

Я також зробив спрощену версію, роздвоєну з прикладу @Theodore Brown, але базуючись лише на цьому CSS.

Не у всіх браузерах використовується ця CSS, хоча: http://caniuse.com/pointer-events

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


Властивість покажчика-події - це правильне рішення, яке рухається вперед, але, на жаль, воно не працює в IE8-IE10, які все ще широко використовуються. Також я повинен зазначити, що ваш поточний jsFiddle навіть не працює в IE11, оскільки він не додає необхідних слухачів подій та попередження поведінки за замовчуванням.
Теодор Браун

2
Використання покажчиків-подій справді є гарною відповіддю, я трохи намагався, перш ніж сам з’ясувати, що відповідь має бути вищим.
floribon

Фантастичний варіант для тих, хто з нас пощастило, що не потрібно підтримувати старі браузери.
Люк Гансфорд

7
Що робити, якщо ваші діти - кнопка? oO
Девід

9
він працює лише в тому випадку, якщо у вас немає інших елементів контролера (редагування, видалення) всередині області, оскільки це рішення також блокує їх ..
Zoltán Süle

45

Ось найпростіше рішення для веб-переглядачів (серйозно):

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

У кількох словах ви створюєте "маску" всередині дрозової зони з успадкованою шириною та висотою, абсолютне положення, яке буде просто показано, коли дравер починається.
Отже, показавши цю маску, ви можете виконати трюк, додавши на неї інші події dragleave & drop.

Залишивши або скинувши, ви просто знову заховаєте маску.
Просто і без ускладнень.

(Прим .: Порада Грега Петтіта. Ви повинні бути впевнені, що маска наводить всю скриньку, включаючи кордон)


Не знаю чому, але це не працює стабільно з Chrome. Іноді залишаючи область, маска залишається видимою.
Грег Петтіт

2
Власне, це кордон. Нехай маска перекриває кордон, не маючи власної межі, і вона повинна працювати добре.
Грег Петтіт

1
Зауважте, у вашій jsfiddle є помилка, у drag_drop вам слід видалити клавішу наведення на "#box" не "# box-a"
ентропія

Це приємне рішення, але якось у мене це не вийшло. Я нарешті з'ясував рішення. Для тих із вас, хто шукає чогось іншого. ви можете спробувати це: github.com/bingjie2680/jquery-draghover
bingjie2680

Ні. Я не хочу втрачати контроль над дочірніми елементами. Я створив простий плагін jQuery, який вирішує проблему, яка виконує роботу за нас. Перевір мою відповідь .
Лука Фагіолі

39

Минуло досить багато часу після того, як це питання було задано, і багато рішень (включаючи некрасиві хаки) надаються.

Мені вдалося виправити ту саму проблему, яку я мала нещодавно завдяки відповіді у цій відповіді, і я подумав, що це може бути корисним для того, хто заходить на цю сторінку. Вся ідея полягає в тому, щоб зберігати 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) набагато більшим, коли файл перетягується на екран, щоб користувач міг легко перекинути файли в потрібне місце.


4
Це найкраще рішення, воно краще, ніж лічильник (особливо якщо ви делегуєте події), і воно також працює з перетягуючими дітьми.
Фред

3
Це не допомагає тхо для проблеми дублювання подій драцентера
BT

Чи працює це для «дітей», які є псевдоелементами css або псевдокласами? Я не міг домогтися цього, але, можливо, я робив це неправильно.
Josh Bleecher Snyder

1
Станом на березень 2020 року мені також вдалося впоратись із цим рішенням. Примітка: деякі лінери можуть скаржитися на використання ==над ===. Ви порівнюєте посилання на цільовий об'єкт події, тому ===добре працює.
Олексій

33

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

На щастя, я був в змозі придумати обхідний шлях , який робить роботу в старих версіях IE. В основному це передбачає виявлення та ігнорування dragleaveподій, які трапляються при перетягуванні над дочірніми елементами. Оскільки dragenterподія запускається на дочірні вузли перед початком dragleaveподії у батьків, до кожного дочірнього вузла можуть бути додані окремі слухачі подій, які додають або видаляють клас "ігнорувати та перетягувати-залишати" з цілі краплі. Тоді dragleaveслухач події, що випадає, може просто ігнорувати дзвінки, які відбуваються, коли існує цей клас. Ось jsFiddle, що демонструє це рішення . Він перевірений і працює в Chrome, Firefox та IE8 +.

Оновлення:

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


Ця відповідь показує вирішення небажаної стрільби, але нехтує питанням: "Чи можна запобігти стрілянині стріляти під час перетягування в дочірній елемент?" повністю.
HD

Поведінка може стати дивною, коли перетягувати файл із-за браузера. У Firefox у мене є "Введення дитини -> Введення батьків -> Вихід з дитини -> Введення дитини -> Вихід з дитини", не виходячи з батька, який залишив клас "над". Старий IE потребує заміни attachEvent для addEventListener.
HD

Це рішення сильно залежить від барботажу, "хибність" у всіх addEventListener слід підкреслити як важливу (хоча це поведінка за замовчуванням), оскільки багато людей можуть не знати про це.
HD

Цей єдиний перетягуючий додає ефект до зони випаду, яка не з’являється, коли dropzone використовується для інших об'єктів, що перетягуються, що не викликає події dragstart. Можливо, використання всього як дроп-зони для ефекту перетягування, зберігаючи реальну зону з іншими обробниками.
HD

1
@HD Я оновив свою відповідь інформацією про використання властивості CSS покажчика-події, щоб запобігти dragleaveзапуску події в Chrome, Firefox та IE11 +. Я також оновив своє інше рішення для підтримки IE8 та IE9, крім IE10. Навмисно, ефект "dropzone" додається лише при перетягуванні посилання "Перетягніть мене". Інші можуть вільно змінювати таку поведінку, як це необхідно для підтримки їх використання.
Теодор Браун

14

Проблема полягає в тому, що 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');
               }
           }

1
Дякую! Однак я не можу змусити це працювати в Chrome. Не могли б ви надати робочу загадку свого хака?
pimvdb

3
Я думав це зробити, перевіривши також координати. Ви зробили більшу частину роботи для мене, thx :). Мені довелося внести деякі корективи:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Крістоф

Він не працюватиме в Chrome, оскільки його події не має e.xта e.y.
Hengjie

Мені подобається це рішення. Спочатку не працював у Firefox. Але якщо замінити ex на e.clientX, а ey на e.clientY, це працює. Також працює в Chrome.
Ніколь Штуц

Не працювали в хромі для мене, ані те, що пропонували Кріс або Даніель Стуц,
Тимо Хуовінен

14

якщо ви використовуєте HTML5, ви можете отримати батьківський клієнтRect:

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


2
Відмінна відповідь.
broc.seib

Дякую приятелю, мені подобається звичайний підхід до JavaScript.
Тім Герхард

При використанні елементів, що мають border-radius, переміщення вказівника близько до кута фактично залишить елемент, але цей код все ще вважатиме, що ми всередині (ми залишили елемент, але ми все ще знаходимось у обмежувальному прямокутнику). Тоді dragleaveобробник події взагалі не буде викликаний.
Джей

11

Дуже просте рішення - використовувати pointer-eventsвластивість CSS . Просто встановіть його значення noneпри перетягуванні кожного дочірнього елемента. Ці елементи більше не запускають пов’язані з мишею події, тому вони не зачеплять мишу за них і, таким чином, не запускають перетягування на батьківщині.

Не забудьте повернути цю властивість autoпісля завершення перетягування;)


11

Це досить просте рішення працює для мене поки що, якщо припустити, що ваша подія приєднана до кожного елемента перетягування окремо.

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}

Це чудово! дякую, що поділився @kenneth, ти врятував мені день! Багато інших відповідей застосовуються лише в тому випадку, якщо у вас є одна зона, що випадає.
Антоні

Для мене це працює в Chrome і Firefox, але ідентифікатор не працює в Edge. Будь ласка, дивіться: jsfiddle.net/iwiss/t9pv24jo
iwis


7

Ви можете виправити це у 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. Ось версія з реалізованим вище кодом.


Дякую велике, але справді це Chrome, я намагаюся вирішити цю проблему дюйма
pimvdb

5
@pimvdb Я бачу, що ви зареєстрували помилку , я просто залишлю тут посилання на випадок, якщо хтось інший натрапить на цю відповідь.
robertc

Я так і зробив, але забув додати тут посилання. Дякуємо за це.
pimvdb

Помилка Chrome виправлена ​​тим часом.
phk

7

І ось це рішення для 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!');
                    }
                })

Чи переходить це в «if (typeof event.clientX === 'undefined')»?
Альдекейн

1
Працював чудово, але над браузером може бути ще одне вікно, тому отримання місця миші та порівняння його з прямокутною зоною екрану недостатньо.
HD

Я погоджуюся з @HD Також це спричинить проблеми, коли елемент має великі розміри, border-radiusяк я пояснив у своєму коментарі до відповіді @ azlar вище.
Джей

7

Ось ще одне рішення за допомогою 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');
   }
}

Сподіваюся, це працює, ось загадка .


3
Для мене це не вийшло. Мені потрібно було event.clientX, event.clientYзамість цього використовувати , оскільки вони відносно вікна перегляду, а не сторінки. Я сподіваюся, що це допоможе іншій загубленій душі там.
Джон Абрамс

7

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


У мене була ця сама проблема, і ваше рішення чудово працює. Порада для інших. Це був мій випадок: якщо дочірнім елементом, який ви намагаєтесь ігнорувати подію перетягування, є клон перетягуваного елемента (намагаючись досягти візуального попереднього перегляду), ви можете використовувати це всередині dragstart під час створення клону :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche

4

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

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

У мене є div, який перекриває всю мою сторінку, коли сторінка завантажується, я приховую її.

коли ви перетягуєте документ, я показую його, і коли ви падаєте на батьків, він обробляє його, і коли ви залишаєте батьківський, я перевіряю х і у.

$('#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;
});

1
Хаккі, але розумні. Мені подобається. Працювали для мене.
cupcakekid

1
О, як би я хотів, щоб ця відповідь мала більше голосів! Дякую
Кірбі

4

Я написав невелику бібліотеку під назвою Dragster, щоб вирішити цю точну проблему, працює скрізь, крім того, що мовчки нічого не роблять в IE (який не підтримує конструкторів подій DOM, але було б досить легко написати щось подібне, використовуючи користувацькі події jQuery)


Дуже корисно (принаймні для мене там, де я дбаю лише про Chrome).
М Кац

4

У мене виникли ті ж проблеми і спробував використовувати рішення pk7s. Це спрацювало, але це можна зробити трохи краще без зайвих елементів купола.

В основному ідея та ж - додайте додаткову невидиму накладку на область, що випадає. Лише це дозволяє робити без зайвих елементів купола. Ось роль, яку прийшли грати псевдоелементи 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/

Я сподіваюся, що це допоможе будь-кому з тією ж проблемою.


1
Іноді не працює на хром (не ловить драглавель належним чином)
Тімо Хуовінен

4

Просто перевірте, чи перетягнутий елемент є дочірнім, якщо він є, тоді не видаляйте клас стилю "драговер". Досить просто і працює для мене:

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })

Схоже, найпростіше для мене рішення, і воно вирішило мою проблему. Дякую!
Франческо Маршетті-Штазі

3

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

Я буду використовувати 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 on parent, ми трохи затримуємо його, перевернувши порядок, тому клас видаляється спочатку, а потім реагується після мілісекунди.


Єдине, що робить цей фрагмент - це перешкоджати видаленню класу "hovered" шляхом його повторного використання наступного циклу (робить подія "dragleave" марною).
null

Це не марно. Якщо ви залишите батьків, це буде працювати, як очікувалося. Сила цього рішення полягає в його простоті, це не ідеально і не найкраще. Кращим рішенням було б позначити вступ на дитину, в режимі onleave, якщо ми щойно ввели дитину, і якщо так, не спрацьовувати залишити подію. Потрібно провести випробування, додаткові охоронці, шахи для онуків тощо
Marcin Raczkowski

3

Я написав модуль перетягування, який називається капати який фіксує, серед інших, цю дивовижну поведінку. Якщо ви шукаєте хороший модуль перетягування низького рівня, який ви можете використовувати як основу для чого завгодно (завантаження файлів, перетягування в програму, перетягування із зовнішніх джерел), слід перевірити це модуль вийшов:

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

2

Чергове робоче рішення, трохи простіше.

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

Схоже, це не завжди працює в Chrome. Я б іноді отримував clientXвище 0, коли миша знаходиться поза коробкою. Звичайно, мої елементиposition:absolute
Hengjie,

Це трапляється постійно чи лише іноді? Тому що якщо миша рухається занадто швидко (наприклад, за вікном), ви можете отримати неправильні значення.
Профіт

Це трапляється 90% часу. Є рідкісні випадки (1 із 10 разів), коли я можу досягти 0. Я знову спробую рухати мишу повільніше, але я не міг сказати, що рухаюся швидко (можливо, те, що ви називали б звичайною швидкістю).
Хенджі

2

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

1

У мене виникла подібна проблема - мій код для приховування зони випаду на події dragleave для тіла вистрілили контактантно, коли наводили дочірні елементи, роблячи мерехтіння краплі зони в 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);

Це я і в кінцевому підсумку робив, але це все ще схематично.
mpen

1

Подія " 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

Вирішили ..!

Оголосити будь-який масив для ex:

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

Не потрібно турбуватися про дочірні елементи.


1

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

У наведеному нижче прикладі у мене є таблиця, в якій я хочу мати можливість обмінюватися вмістом рядків таблиці один з одним за допомогою 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;
  }
}

У коді залишилися дуже основні коментарі, такі, що цей код підходить і для початківців. Сподіваюсь, це допоможе вам! Зауважте, що вам, звичайно, потрібно буде додати всіх слухачів подій, про яких я згадував вище, у кожну клітинку таблиці, щоб це працювало.


0

Ось ще один підхід, заснований на термінах подій.

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

pimvdb ..

Чому б вам не спробувати використовувати краплі замість драглей . Це працювало для мене. сподіваюся, що це вирішить вашу проблему.

Перевірте 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");
                 }
                });

2
Якщо користувач передумає і перетягне файл назад із браузера, він залишиться червоним.
ZachB

0

Ви можете використовувати тайм-аут із 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/


0

Потрібно видалити події вказівника для всіх дочірніх об'єктів цілі перетягування.

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) {
                //
            }
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.