Dragleave HTML5 jest uruchamiany po najechaniu na element potomny

300

Problem, który mam, polega na tym, że dragleavezdarzenie elementu jest uruchamiane podczas najechania na element potomny tego elementu. Ponadto dragenternie jest uruchamiany po ponownym najechaniu na element nadrzędny z powrotem.

Zrobiłem uproszczone skrzypce: 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>

z następującym JavaScriptem:

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

Powinien to zrobić, powiadamiając użytkownika, czyniąc kroplę divczerwoną podczas przeciągania czegoś tam. To działa, ale jeśli przeciągniesz na pdziecko, dragleavezostanie ono zwolnione i divnie będzie już czerwone. Przejście z powrotem do kropli divrównież nie powoduje, że znów jest czerwone. Konieczne jest całkowite opuszczenie upuszczenia divi przeciągnięcie z powrotem do niego, aby był czerwony.

Czy można zapobiec dragleaveuruchamianiu podczas przeciągania do elementu potomnego?

Aktualizacja 2017: TL; DR, pointer-events: none;wyszukaj CSS zgodnie z opisem w odpowiedzi @ HD poniżej, która działa w nowoczesnych przeglądarkach i IE11.

pimvdb
źródło
Zgłoszony błąd pimvdb nadal istnieje w Webkit od maja 2012 r. Sprzeciwiłem się temu, dodając także klasę w dragover, co wcale nie jest tak fajne, ponieważ tak często odpala, ale wydaje się, że nieco to załatało.
ajm
2
@ajm: Dzięki, to działa do pewnego stopnia. Jednak w Chrome pojawia się błysk podczas wchodzenia lub wychodzenia z elementu potomnego, prawdopodobnie dlatego, dragleaveże w tym przypadku jest nadal uruchamiany.
pimvdb
Otworzyłem aprobaty błędów interfejsu użytkownika jQuery są mile widziane, więc mogą zdecydować o włożeniu na nie zasobów
fguillen
@fguillen: Przykro mi, ale nie ma to nic wspólnego z interfejsem jQuery. W rzeczywistości jQuery nie jest nawet potrzebny do uruchomienia błędu. Złożyłem już błąd WebKit, ale na razie nie ma aktualizacji.
pimvdb
2
@pimvdb Dlaczego nie akceptujesz odpowiedzi HD?
TylerH

Odpowiedzi:

337

Musisz tylko zachować licznik referencyjny, zwiększyć go, gdy pojawi się dragenter, zmniejszyć, gdy dostaniesz dragleave. Gdy licznik ma wartość 0 - usuń klasę.

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

Uwaga: W przypadku upuszczenia zresetuj licznik do zera i wyczyść dodaną klasę.

Możesz go uruchomić tutaj

Drzewiasty
źródło
8
OMG to jest najbardziej oczywiste rozwiązanie i miało tylko JEDEN głos ... Chodźcie ludzie, możecie zrobić lepiej. Myślałem o tym, ale po zobaczeniu poziomu wyrafinowania pierwszych kilku odpowiedzi prawie go odrzuciłem. Czy miałeś jakieś wady?
Arthur Corenzan
11
Nie działało to, gdy krawędź przeciąganego elementu dotyka krawędzi innego przeciąganego elementu. Na przykład lista sortowalna; przeciągnięcie elementu w dół, nad kolejnym elementem, który można przeciągnąć, nie zmniejsza licznika z powrotem do 0, zamiast tego blokuje się na 1. Ale jeśli przeciągnę go na boki, z dowolnego innego elementu, który można przeciągać, zadziała. Poszedłem z pointer-events: nonedziećmi. Kiedy zaczynam przeciągać, dołączam klasę, która ma tę właściwość, a kiedy przeciąganie się kończy, usuwam klasę. Działał ładnie w Safari i Chrome, ale nie w Firefox.
Arthur Corenzan
13
Działa to dla mnie we wszystkich przeglądarkach, ale Firefox. Mam nowe rozwiązanie, które działa wszędzie w moim przypadku. Przy pierwszym dragenterzapisuję event.currentTargetw nowej zmiennej dragEnterTarget. Dopóki dragEnterTargetjest ustawiony, ignoruję dalsze dragenterwydarzenia, ponieważ pochodzą one od dzieci. We wszystkich dragleaveprzypadkach sprawdzam dragEnterTarget === event.target. Jeśli to nieprawda, zdarzenie zostanie zignorowane, ponieważ zostało wystrzelone przez dziecko. Jeśli to prawda, resetuję dragEnterTargetdo undefined.
Pipo
3
Świetne rozwiązanie. Musiałem zresetować licznik do 0 w funkcji obsługującej przyjmowanie upuszczenia, w przeciwnym razie kolejne przeciągnięcia nie działałyby zgodnie z oczekiwaniami.
Liam Johnston,
2
@ Woody Nie widziałem jego komentarza na ogromnej liście komentarzy, ale tak, to jest poprawka. Dlaczego nie uwzględnić tego w swojej odpowiedzi?
BT
93

Czy można zapobiec strzelaniu dragleave podczas przeciągania do elementu potomnego?

Tak.

#drop * {pointer-events: none;}

Ten CSS wydaje się wystarczający dla Chrome.

Podczas używania go z Firefoksem, #drop nie powinien mieć bezpośrednio węzłów tekstowych (w innym przypadku występuje dziwny problem polegający na tym, że element „zostaw to sobie” ), więc sugeruję pozostawić go tylko z jednym elementem (np. Użyj div wewnątrz #drop, aby umieścić wszystko w środku)

Oto jsfiddle rozwiązujący oryginalny przykład (zepsuty) przykład .

Stworzyłem również uproszczoną wersję , opracowaną na podstawie przykładu @Theodore Brown, ale opartą tylko na tym CSS.

Jednak nie wszystkie przeglądarki mają zaimplementowany ten CSS: http://caniuse.com/pointer-events

Widząc kod źródłowy Facebooka, mogłem go znaleźć pointer-events: none;kilka razy, jednak prawdopodobnie jest on używany razem z płynnymi awariami degradacji. Przynajmniej jest to takie proste i rozwiązuje problem w wielu środowiskach.

HD
źródło
Właściwość wskaźnik-zdarzenia jest właściwym rozwiązaniem na przyszłość, ale niestety nie działa w IE8-IE10, które są nadal powszechnie używane. Powinienem również zauważyć, że twój obecny jsFiddle nie działa nawet w IE11, ponieważ nie dodaje niezbędnych detektorów zdarzeń i domyślnego zapobiegania zachowaniu.
Theodore Brown,
2
Korzystanie ze zdarzeń wskaźnikowych jest rzeczywiście dobrą odpowiedzią, trochę się zmagałem, zanim sam się przekonałem, że odpowiedź powinna być wyższa.
floribon
Fantastyczna opcja dla tych z nas, którzy mają szczęście, że nie muszą obsługiwać starych przeglądarek.
Luke Hansford
7
Co jeśli twoje dzieci są guzikiem? oO
David
9
to działa tylko wtedy, gdy nie ma innych elementów kontrolera (edycja, usuwanie) wewnątrz obszaru, ponieważ to rozwiązanie blokuje ich zbyt ..
Zoltán Süle
45

Oto najprostsze rozwiązanie dla wielu przeglądarek (poważnie):

jsfiddle <- spróbuj przeciągnąć jakiś plik do pudełka

Możesz zrobić coś takiego:

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

W kilku słowach tworzysz „maskę” w strefie zrzutu, z odziedziczoną szerokością i wysokością, pozycją bezwzględną, która pokaże się po rozpoczęciu przeciągania.
Po pokazaniu tej maski możesz wykonać tę sztuczkę, dołączając do niej inne zdarzenia dragleave i drop.

Po wyjściu lub upuszczeniu po prostu ponownie ukryjesz maskę.
Prosty i bez komplikacji.

(Obs .: Porada Greg Pettit - Musisz upewnić się, że maska ​​unosi całe pole, w tym ramkę)

Diego T. Yamaguchi
źródło
Nie jestem pewien, dlaczego, ale nie działa spójnie z Chrome. Czasami opuszczenie obszaru powoduje, że maska ​​jest widoczna.
Greg Pettit
2
W rzeczywistości jest to granica. Niech maska ​​nakłada się na granicę, bez własnej granicy, i powinna działać OK.
Greg Pettit
1
Zauważ, że twój jsfiddle ma błąd, w drag_drop powinieneś usunąć klasę aktywowania na "# box" nie "# box-a"
entropia
To fajne rozwiązanie, ale jakoś mi się nie udało. W końcu wymyśliłem obejście. Dla tych z Was, którzy szukają czegoś innego. możesz spróbować: github.com/bingjie2680/jquery-draghover
bingjie2680
Nie. Nie chcę stracić kontroli nad elementami potomnymi. Stworzyłem prostą wtyczkę jQuery, która rozwiązuje problem związany z wykonywaniem pracy za nas. Sprawdzić moją odpowiedź .
Luca Fagioli,
39

Minęło sporo czasu, od kiedy pytanie zostało zadane i zapewniono wiele rozwiązań (w tym brzydkie hacki).

Udało mi się naprawić ten sam problem, który miałem ostatnio dzięki odpowiedzi w tej odpowiedzi i pomyślałem, że może być pomocny dla kogoś, kto przejdzie na tę stronę. Cała idea jest przechowywanie evenet.targetw ondrageenterkażdym razem to się nazywa na którykolwiek z elementów rodzica lub dziecka. Następnie ondragleavesprawdź, czy bieżący cel ( event.target) jest równy obiektowi, w którym zapisałeś ondragenter.

Jedynym przypadkiem, w którym te dwa są dopasowane, jest opuszczenie okna przeglądarki przez przeciągnięcie.

Powodem, dla którego działa to dobrze, jest to, że mysz opuszcza element (powiedzmy el1) i wchodzi do innego elementu (powiedzmy el2), najpierw el2.ondragenterwywoływane jest, a następnie el1.ondragleave. Tylko wtedy, gdy przeciąganie opuszcza / wchodzi do okna przeglądarki, event.targetbędzie ''w obu el2.ondragenter i el1.ondragleave.

Oto moja działająca próbka. Przetestowałem to na IE9 +, Chrome, Firefox i 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;
    };
})();

A oto prosta strona 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>

Przy odpowiedniej stylizacji zrobiłem, aby wewnętrzny div (# file-drop-area) był znacznie większy za każdym razem, gdy plik jest przeciągany na ekran, aby użytkownik mógł łatwo upuścić pliki w odpowiednie miejsce.

Ali Motevallian
źródło
4
To najlepsze rozwiązanie, lepsze niż licznik (zwłaszcza jeśli delegujesz wydarzenia) i działa również z dziećmi przeciąganymi.
Fred,
3
To nie pomaga w przypadku problemu zduplikowanych zdarzeń dragenter
BT
Czy działa to dla „dzieci”, które są pseudoelementami css lub pseudoklasami? Nie mogłem tego zrobić, ale może robiłem to źle.
Josh Bleecher Snyder
1
Od marca 2020 r. Miałem również szczęście z tym rozwiązaniem. Uwaga: niektóre linters mogą narzekać na użycie ==over ===. Porównujesz odwołania do obiektu docelowego zdarzenia, więc też ===działa dobrze.
Alex
33

„Właściwym” sposobem rozwiązania tego problemu jest wyłączenie zdarzeń wskaźnika na elementach potomnych celu upuszczenia (jak w odpowiedzi @ HD). Oto jsFiddle, który stworzyłem, który demonstruje tę technikę . Niestety nie działa to w wersjach programu Internet Explorer wcześniejszych niż IE11, ponieważ nie obsługiwały one zdarzeń wskaźnika .

Na szczęście udało mi się wymyślić obejście który robi pracy w starszych wersjach IE. Zasadniczo polega na identyfikowaniu i ignorowaniu dragleavezdarzeń, które występują podczas przeciągania po elementach potomnych. Ponieważ dragenterzdarzenie jest uruchamiane w węzłach podrzędnych przed dragleavezdarzeniem w obiekcie nadrzędnym, do każdego węzła podrzędnego można dodać osobne detektory zdarzeń, które dodają lub usuwają klasę „ignoruj-przeciągnij-zostaw” z miejsca docelowego upuszczania. Następnie dragleavedetektor zdarzeń celu upuszczania może po prostu zignorować wywołania, które występują, gdy ta klasa istnieje. Oto jsFiddle demonstrujący to obejście . Jest testowany i działa w Chrome, Firefox i IE8 +.

Aktualizacja:

Stworzyłem jsFiddle demonstrujący połączone rozwiązanie wykorzystujące wykrywanie funkcji, w którym zdarzenia wskaźnika są używane, jeśli są obsługiwane (obecnie Chrome, Firefox i IE11), a przeglądarka wraca do dodawania zdarzeń do węzłów potomnych, jeśli obsługa zdarzeń wskaźnika nie jest dostępna (IE8 -10).

Theodore Brown
źródło
Ta odpowiedź pokazuje obejście dla niepożądanego strzelania, ale pomija pytanie „Czy można zapobiec strzelaniu przez Dragleave podczas przeciągania do elementu potomnego?” całkowicie.
HD
Zachowanie może stać się dziwne podczas przeciągania pliku spoza przeglądarki. W Firefoksie mam „Wchodzę w dziecko -> Wchodzę w dziecko -> Wychodzę - - Wchodzę w dziecko -> Wychodzę w dziecko” bez wychodzenia z rodzica, który odszedł z klasą „nad”. Stara IE wymagałaby zastąpienia attachEvent dla addEventListener.
HD
To rozwiązanie zależy w dużej mierze od propagacji. Należy podkreślić „fałsz” we wszystkich addEventListener jako istotny (chociaż jest to zachowanie domyślne), ponieważ wiele osób może o tym nie wiedzieć.
HD
Ten pojedynczy przeciągalny dodaje efekt do strefy zrzutu, który nie pojawia się, gdy jest on używany dla innych obiektów przeciąganych, które nie wyzwalają zdarzenia przeciągania. Być może użycie wszystkiego jako strefy zrzutu dla efektu przeciągania przy jednoczesnym zachowaniu prawdziwej strefy zrzutu dla innych programów obsługi zrobiłoby to.
HD
1
@HD Zaktualizowałem swoją odpowiedź informacjami o korzystaniu z właściwości CSS wskaźnika-zdarzeń, aby zapobiec uruchomieniu dragleavezdarzenia w Chrome, Firefox i IE11 +. Zaktualizowałem również inne moje obejście, aby obsługiwało IE8 i IE9, oprócz IE10. Celowe jest, aby efekt dropzone był dodawany tylko podczas przeciągania linku „Przeciągnij mnie”. Inni mogą swobodnie zmieniać to zachowanie w razie potrzeby w celu obsługi swoich przypadków użycia.
Theodore Brown
14

Problem polega na tym, że dragleavezdarzenie jest uruchamiane, gdy mysz znajdzie się przed elementem potomnym.

Próbowałem różnych metod sprawdzania, czy e.targetelement jest taki sam jak thiselement, ale nie mogłem uzyskać żadnej poprawy.

Sposób, w jaki rozwiązałem ten problem, był trochę włamaniem, ale działa w 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');
               }
           }
Greg
źródło
1
Dzięki! Nie mogę jednak tego uruchomić w Chrome. Czy mógłbyś zapewnić działające skrzypce swojego hacka?
pimvdb
3
Zastanawiałem się nad tym, sprawdzając również struny. Wykonałeś dla mnie większość pracy, dzięki :). Musiałem jednak wprowadzić pewne poprawki:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof
To nie będzie działać w Chrome, ponieważ nie ma zdarzenia e.xi e.y.
Hengjie,
Podoba mi się to rozwiązanie. Najpierw nie działał w przeglądarce Firefox. Ale jeśli zamienisz ex na e.clientX, a ey na e.clientY, to zadziała. Działa również w Chrome.
Nicole Stutz
Nie działały dla mnie w chromie, podobnie jak sugerowali Chris i Daniel Stuts
Timo Huovinen
14

jeśli używasz HTML5, możesz uzyskać klienta nadrzędnego:

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

Następnie w parent.dragleave ():

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

tutaj jest jsfiddle

azlar
źródło
2
Doskonała odpowiedź.
broc.seib
Dzięki kolego, lubię proste podejście do javascript.
Tim Gerhard
Podczas korzystania z elementów posiadających border-radiusprzesuwanie wskaźnika blisko narożnika faktycznie opuszczałoby element, ale ten kod nadal myślałby, że jesteśmy w środku (opuściliśmy element, ale nadal znajdujemy się w prostokącie ograniczającym). Wtedy dragleavemoduł obsługi zdarzeń nie zostanie w ogóle wywołany.
Jay Dadhania,
11

Bardzo prostym rozwiązaniem jest użycie pointer-eventswłaściwości CSS . Po prostu ustaw jego wartość nonena dragstart na każdym elemencie potomnym. Te elementy nie będą już wywoływać zdarzeń związanych z myszą, więc nie złapią myszy nad nimi, a tym samym nie wywołają dragleave na rodzicu.

Nie zapomnij ustawić tej właściwości z powrotem na autozakończenie przeciągania;)

FruityFred
źródło
11

To dość proste rozwiązanie do tej pory działa dla mnie, zakładając, że Twoje zdarzenie jest dołączane do każdego elementu przeciągania indywidualnie.

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}
Kenneth Spencer
źródło
To jest świetne! dzięki za udostępnienie @kenneth, uratowałeś mi dzień! Wiele innych odpowiedzi dotyczy tylko jednej strefy zrzutu.
antoni
Dla mnie działa w Chrome i Firefox, ale id nie działa w Edge. Zobacz: jsfiddle.net/iwiss/t9pv24jo
iwis
8

Bardzo proste rozwiązanie:

parent.addEventListener('dragleave', function(evt) {
    if (!parent.contains(evt.relatedTarget)) {
        // Here it is only dragleave on the parent
    }
}
Owen M.
źródło
Wydaje się, że to nie działa w Safari 12.1: jsfiddle.net/6d0qc87m
Adam Taylor
7

Możesz to naprawić w Firefoksie, inspirując się kodem źródłowym 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');
        }
    }

}

Niestety nie działa w Chrome, ponieważ relatedTargetwydaje się, że nie istnieje w przypadku dragleavewydarzeń, i zakładam, że pracujesz w Chrome, ponieważ twój przykład nie działał w Firefox. Oto wersja z powyższym kodem.

robertc
źródło
Wielkie
5
@pimvdb Widzę, że zarejestrowałeś błąd , po prostu zostawię tutaj odniesienie na wypadek, gdyby ktokolwiek spotkał się z tą odpowiedzią.
robertc
Rzeczywiście tak zrobiłem, ale zapomniałem dodać tutaj link. Dzięki za zrobienie tego.
pimvdb,
Błąd Chrome został naprawiony w międzyczasie.
phk
7

I oto jest rozwiązanie dla 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!');
                    }
                })
Aldekein
źródło
Czy przechodzi do «if (typeof event.clientX === 'undefined')»?
Aldekein,
1
Działało ładnie, ale nad przeglądarką mogło znajdować się inne okno, więc ustalenie lokalizacji myszy i porównanie jej z prostokątnym obszarem ekranu nie wystarczy.
HD
Zgadzam się z @HD Ponadto spowoduje to problemy, gdy element będzie duży, border-radiusjak wyjaśniłem w moim komentarzu do odpowiedzi @ azlar powyżej.
Jay Dadhania,
7

Oto inne rozwiązanie za pomocą 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');
   }
}

Mam nadzieję, że to zadziała , oto skrzypce .

marcelj
źródło
3
To nie działało dla mnie po wyjęciu z pudełka. Musiałem użyć event.clientX, event.clientYzamiast tego, ponieważ są one związane z oknem ekranu, a nie ze stroną. Mam nadzieję, że pomoże to innej zagubionej duszy.
Jon Abrams,
7

Prostym rozwiązaniem jest dodanie reguły css pointer-events: nonedo komponentu potomnego, aby zapobiec wyzwalaniu ondragleave. Zobacz przykład:

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>

Alexandre Annic
źródło
Miałem ten sam problem i twoje rozwiązanie działa świetnie. Wskazówka dla innych, tak było w moim przypadku: jeśli element potomny, który próbujesz zignorować zdarzeniem przeciągania , jest klonem przeciąganego elementu (próbując uzyskać podgląd wizualny), możesz użyć tego w trakcie przeciągania podczas tworzenia klonu :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche,
4

Nie jestem pewien, czy ta przeglądarka działa, ale przetestowałem w Chrome i to rozwiązuje mój problem:

Chcę przeciągnąć i upuścić plik na całej stronie, ale mój dragleave jest uruchamiany, gdy przeciągam element podrzędny. Moją poprawką było spojrzenie na xiy myszy:

Mam div, który nakłada się na całą moją stronę, kiedy strona się ładuje, ukrywam ją.

kiedy przeciągasz dokument, pokazuję go, a kiedy upuszczasz nadrzędnego, on go obsługuje, a kiedy opuszczasz nadrzędnego, sprawdzam xiy.

$('#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;
});
Chrisrisick
źródło
1
Zuchwały, ale sprytny. Podoba mi się. Pracował dla mnie.
cupcakekid
1
Och, jak chciałbym, żeby ta odpowiedź miała więcej głosów! Dziękuję
Kirby,
4

Napisałem małą bibliotekę o nazwie Dragster, aby poradzić sobie z tym dokładnie problemem, działa wszędzie oprócz cichej pracy w IE (która nie obsługuje konstruktorów zdarzeń DOM, ale napisanie czegoś podobnego przy użyciu niestandardowych zdarzeń jQuery byłoby dość łatwe)

Ben
źródło
Bardzo przydatne (przynajmniej dla mnie, gdy zależy mi tylko na Chrome).
M Katz
4

Miałem ten sam problem i próbowałem użyć rozwiązania pk7s. Działało, ale można to zrobić trochę lepiej bez dodatkowych elementów dom.

Zasadniczo pomysł jest taki sam - dodaj dodatkową niewidoczną nakładkę na obszar upuszczalny. Zróbmy to tylko bez dodatkowych elementów dom. Oto część, w której pojawiają się pseudoelementy 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

Ta reguła po utworzy w pełni zakrytą nakładkę na obszar, który można upuścić.

#box.overlay:after {
    content:'';
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 1;
}

Oto pełne rozwiązanie: http://jsfiddle.net/F6GDq/8/

Mam nadzieję, że pomoże każdemu z tym samym problemem.

rasmusx
źródło
1
Czasami nie działa na chromie (nie łapie poprawnie dragleave)
Timo Huovinen
4

Po prostu sprawdź, czy przeciągnięty element jest dzieckiem, jeśli tak, to nie usuwaj klasy stylu „przeciągnij”. Całkiem proste i działa dla mnie:

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })
sofarsoghood
źródło
Wydaje mi się to najprostszym rozwiązaniem i rozwiązało mój problem. Dzięki!
Francesco Marchetti-Stasi
3

Natknąłem się na ten sam problem i oto moje rozwiązanie - które moim zdaniem jest znacznie łatwiejsze niż powyżej. Nie jestem pewien, czy to crossbrowser (może zależeć nawet od kolejności propagacji)

Użyję jQuery dla uproszczenia, ale rozwiązanie powinno być niezależne od frameworka.

Wydarzenie przechodzi do nadrzędnego w obu kierunkach, dlatego podano:

<div class="parent">Parent <span>Child</span></div>

Dołączamy wydarzenia

el = $('.parent')
setHover = function(){ el.addClass('hovered') }
onEnter  = function(){ setTimeout(setHover, 1) }
onLeave  = function(){ el.removeClass('hovered') } 
$('.parent').bind('dragenter', onEnter).bind('dragleave', onLeave)

I o to chodzi. :) działa, ponieważ chociaż onEnter na potomkach odpala przed onLeave na rodzicach, opóźniamy je nieco odwracając kolejność, więc klasa jest najpierw usuwana, a następnie ponownie stosowana po milisekundie.

Marcin Raczkowski
źródło
Jedyne, co robi ten fragment, to uniemożliwienie usunięcia klasy „hovers” poprzez ponowne zastosowanie jej w następnym cyklu tykania (czyniąc zdarzenie „dragleave” bezużytecznym).
null
To nie jest bezużyteczne. Jeśli opuścisz rodzica, będzie działać zgodnie z oczekiwaniami. Zaletą tego rozwiązania jest jego prostota, nie jest idealny ani najlepszy. Lepszym rozwiązaniem byłoby oznaczenie wpisu na dziecku, sprawdzając na bieżąco, czy właśnie weszliśmy do dziecka, a jeśli nie, uruchom zdarzenie opuszczenia. Będzie jednak potrzebował testów, dodatkowych strażników, kibicowania wnukom itp.
Marcin Raczkowski
3

Napisałem moduł przeciągnij i upuść o nazwie kroplówka, który naprawia to dziwne zachowanie, między innymi. Jeśli szukasz dobrego niskopoziomowego modułu przeciągania i upuszczania, którego możesz użyć jako podstawy do wszystkiego (przesyłanie plików, przeciąganie i upuszczanie w aplikacji, przeciąganie zi do źródeł zewnętrznych), powinieneś to sprawdzić wyjście modułu:

https://github.com/fresheneesz/drip-drop

Oto jak zrobiłbyś to, co próbujesz zrobić kroplówką:

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

Aby to zrobić bez biblioteki, zastosowałem technikę liczenia w kroplówce, ponieważ najwyżej oceniana odpowiedź pomija ważne kroki, które spowodują, że wszystko się zepsuje, z wyjątkiem pierwszej kropli. Oto jak to zrobić poprawnie:

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
    }
});
BT
źródło
2

Alternatywne rozwiązanie robocze, trochę prostsze.

//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) {
Profet
źródło
Nie zawsze działa to w Chrome. Czasami dostawałbym clientXpowyżej zera, gdy mysz jest poza polem. Oczywiście moje elementy toposition:absolute
Hengjie,
Czy zdarza się to cały czas, czy tylko czasami? Ponieważ jeśli mysz porusza się zbyt szybko (np. Za oknem), możesz otrzymać błędne wartości.
Profet
Zdarza się to w 90% przypadków. Są rzadkie przypadki (1 na 10 razy), w których mogę sprawić, że osiągnie 0. Spróbuję ponownie przesunąć mysz wolniej, ale nie mogłem powiedzieć, że poruszałem się szybko (być może to, co nazwałbyś normalną prędkością).
Hengjie
2

Po spędzeniu tylu godzin otrzymałem sugestię, która działa dokładnie tak, jak powinna. Chciałem dać sygnał tylko wtedy, gdy pliki zostały przeciągnięte, a przeciąganie dokumentów, dragleave powodował bolesne migotanie w przeglądarce Chrome.

Tak to rozwiązałem, dodając odpowiednie wskazówki dla użytkownika.

$(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
});
odwiedzinb
źródło
1

Miałem podobny problem - mój kod służący do ukrywania dropzone na zdarzeniu dragleave dla ciała został wystrzelony w sposób ciągły po najechaniu na elementy potomne powodujące migotanie dropzone w Google Chrome.

Udało mi się to rozwiązać, planując funkcję ukrywania dropzone zamiast wywoływania go od razu. Następnie, jeśli zostanie wyrzucony inny dragover lub dragleave, zaplanowane wywołanie funkcji zostanie anulowane.

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);
Arsen
źródło
To też skończyło, ale wciąż jest to szkicowe.
MPpl
1

Zdarzeniedragleave ” jest uruchamiane, gdy wskaźnik myszy wychodzi z obszaru przeciągania kontenera docelowego.

To ma sens, ponieważ w wielu przypadkach tylko rodzic może być upuszczalny, a nie potomkowie. Myślę, że event.stopPropogation () powinien był obsłużyć ten przypadek, ale wygląda na to, że to nie działa.

Wyżej wspomniane niektóre rozwiązania wydają się działać w większości przypadków, ale zawodzą w przypadku tych dzieci, które nie obsługują zdarzeń dragenter / dragleave , takich jak iframe .

1 obejście polega na sprawdzeniu event.relatedTarget i sprawdzeniu, czy znajduje się on w kontenerze, a następnie zignoruj zdarzenie dragleave , tak jak to zrobiłem tutaj:

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

Można znaleźć skrzypce pracuje tutaj !

Lalit Nankani
źródło
1

Rozwiązany ..!

Zadeklaruj dowolną tablicę np.

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

Nie musisz martwić się o elementy potomne.

Shubham Rajodiya
źródło
1

Miałem z tym DUŻO problemów, nawet po przeczytaniu wszystkich tych odpowiedzi, i pomyślałem, że mogę podzielić się z tobą moim rozwiązaniem, ponieważ uznałem, że może to być jedno z prostszych podejść, choć nieco inne. dragleaveMiałem na myśli po prostu całkowite pominięcie nasłuchiwania zdarzeń i kodowanie zachowania dragleave przy każdym nowym zdarzeniu dragenter, jednocześnie upewniając się, że zdarzenia dragenter nie będą uruchamiane niepotrzebnie.

W poniższym przykładzie mam tabelę, w której chcę móc wymieniać zawartość wiersza tabeli za pomocą interfejsu API przeciągnij i upuść. Włącz dragenter, klasa CSS zostanie dodana do elementu wiersza, do którego obecnie przeciągasz swój element, aby go podświetlić, a następnie dragleaveta klasa zostanie usunięta.

Przykład:

Bardzo podstawowa tabela HTML:

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

A funkcja obsługi zdarzenia dragenter, dodane do każdej komórki tabeli (na bok dragstart, dragover, dropi dragendteleskopowe, które nie są specyficzne dla tej kwestii, więc nie skopiowane tutaj):

/*##############################################################################
##                              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;
  }
}

Pozostały bardzo podstawowe komentarze w kodzie, dzięki czemu ten kod jest odpowiedni również dla początkujących. Mam nadzieję, że to ci pomoże! Pamiętaj, że musisz oczywiście dodać wszystkie detektory zdarzeń, o których wspomniałem powyżej, do każdej komórki tabeli, aby to zadziałało.

DevelJoe
źródło
0

Oto inne podejście oparte na czasie wydarzeń.

dragenterZdarzenie wysyłane z elementu dziecka mogą być przechwytywane przez elementu nadrzędnego i zawsze występuje przed dragleave. Czas pomiędzy tymi dwoma zdarzeniami jest naprawdę krótki, krótszy niż jakakolwiek możliwa akcja myszy ludzkiej. Chodzi o to, aby zapamiętać czas wystąpienia zdarzenia dragenteri filtrować dragleavezdarzenia, które występują „niezbyt szybko” po ...

Ten krótki przykład działa w Chrome i 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
    }
})
smrtl
źródło
0

pimvdb ..

Dlaczego nie spróbujesz użyć kropli zamiast dragleave . To zadziałało dla mnie. mam nadzieję, że to rozwiąże twój problem.

Sprawdź 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");
                 }
                });
abhi
źródło
2
Jeśli użytkownik zmieni zdanie i wyciągnie plik z przeglądarki, pozostanie on czerwony.
ZachB
0

Możesz użyć limitu czasu z transitioningflagą i nasłuchiwać na górnym elemencie. dragenter/ dragleavez wydarzeń podrzędnych zostaną przeniesione do kontenera.

Ponieważ dragenterna elemencie potomnym wystrzeliwanym przed dragleavekontenerem ustawimy wyświetlanie flagi jako przejście na 1 ms ... dragleavenasłuchujący sprawdzi flagę przed upływem 1 ms.

Flaga będzie prawdziwa tylko podczas przejścia do elementów potomnych i nie będzie prawdziwa podczas przejścia do elementu nadrzędnego (kontenera)

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/

ilovett
źródło
0

Musisz usunąć zdarzenia wskaźnika dla wszystkich obiektów podrzędnych celu przeciągania.

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) {
                //
            }
        }
    }
EddieB
źródło