Dragleave HTML5 dipecat saat melayang elemen anak

301

Masalah yang saya alami adalah bahwa dragleaveperistiwa elemen dipecat ketika melayang elemen anak dari elemen itu. Juga, dragentertidak dipecat ketika melayang kembali elemen induk lagi.

Saya membuat biola yang disederhanakan: 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>

dengan JavaScript berikut:

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

Apa yang seharusnya dilakukan adalah memberi tahu pengguna dengan membuat drop divred saat menyeret sesuatu ke sana. Ini bekerja, tetapi jika Anda menyeret ke panak, dragleaveitu dipecat dan divtidak merah lagi. Kembali ke drop divjuga tidak membuatnya merah lagi. Sangat penting untuk keluar sepenuhnya dari drop divdan menyeret kembali ke dalamnya untuk membuatnya merah.

Apakah mungkin untuk mencegah dragleavemenembak ketika menyeret elemen anak?

Pembaruan 2017: TL; DR, Cari CSS pointer-events: none;seperti dijelaskan dalam jawaban @ HD di bawah ini yang berfungsi di browser modern dan IE11.

pimvdb
sumber
Bug pimvdb yang dilaporkan masih ada di Webkit pada Mei 2012. Saya telah mengatasinya dengan menambahkan kelas di dragover, yang tidak berada di dekat tempat yang bagus karena sering menyala, tetapi tampaknya sedikit memperbaiki masalah ini.
ajm
2
@ajm: Terima kasih, itu berfungsi sampai batas tertentu. Namun, di Chrome, ada lampu kilat saat memasuki atau meninggalkan elemen anak, mungkin karena dragleavemasih menyala dalam kasus itu.
pimvdb
Saya telah membuka upvotes bug jQuery UI dipersilahkan sehingga mereka dapat memutuskan untuk menempatkan sumber daya di atasnya
fguillen
@fguillen: Maaf, tapi ini tidak ada hubungannya dengan jQuery UI. Bahkan, jQuery bahkan tidak diperlukan untuk memicu bug. Saya sudah mengajukan bug WebKit tetapi tidak ada pembaruan sampai sekarang.
pimvdb
2
@ pimvdb Mengapa Anda tidak menerima jawaban HD?
TylerH

Jawaban:

339

Anda hanya perlu menyimpan penghitung referensi, menambahkannya ketika Anda mendapatkan dragenter, mengurangi ketika Anda mendapatkan dragleave. Saat penghitung berada di 0 - hapus kelas.

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

Catatan: Dalam acara drop, setel ulang penghitung ke nol, dan hapus kelas yang ditambahkan.

Anda bisa menjalankannya di sini

Woody
sumber
8
OMG ini adalah solusi yang paling jelas dan hanya memiliki SATU suara ... Ayo orang-orang, Anda bisa melakukan yang lebih baik. Saya memikirkan hal itu tetapi setelah melihat tingkat kecanggihan dari beberapa jawaban pertama saya hampir membuangnya. Apakah Anda memiliki kekurangan?
Arthur Corenzan
11
Ini tidak berfungsi ketika ujung elemen yang diseret menyentuh tepi elemen yang dapat diseret lainnya. Misalnya daftar yang dapat disortir; menyeret elemen ke bawah, pada item yang dapat diseret berikutnya tidak mengurangi penghitung kembali ke 0, melainkan tersangkut pada 1. Namun jika saya seret ke samping, keluar dari elemen yang dapat diseret lainnya, ia berfungsi. Saya pergi dengan pointer-events: noneanak-anak. Ketika saya mulai menyeret saya menambahkan kelas yang memiliki properti ini, ketika seret saya menghapus kelas. Bekerja dengan baik di Safari dan Chrome, tetapi tidak di Firefox.
Arthur Corenzan
13
Ini bekerja untuk saya di semua browser, tetapi Firefox. Saya memiliki solusi baru yang berfungsi di mana-mana dalam kasus saya. Yang pertama dragentersaya simpan event.currentTargetdalam variabel baru dragEnterTarget. Selama dragEnterTargetdiatur, saya mengabaikan dragenteracara lebih lanjut , karena mereka dari anak-anak. Dalam semua dragleaveacara saya periksa dragEnterTarget === event.target. Jika ini salah acara akan diabaikan karena dipecat oleh seorang anak. Jika ini benar, saya mengatur ulang dragEnterTargetke undefined.
Pipo
3
Solusi bagus Saya perlu mengatur ulang penghitung ke 0 pada fungsi yang menangani penerimaan drop, jika tidak, drag berikutnya tidak bekerja seperti yang diharapkan.
Liam Johnston
2
@ Woody Saya tidak melihat komentarnya di daftar besar komentar di sini, tapi ya, itu perbaikannya. Mengapa tidak memasukkan itu ke dalam jawaban Anda?
BT
93

Apakah mungkin untuk mencegah dragleave dari menembak ketika menyeret ke elemen anak?

Iya.

#drop * {pointer-events: none;}

CSS itu tampaknya cukup untuk Chrome.

Saat menggunakannya dengan Firefox, #drop seharusnya tidak memiliki node teks secara langsung (kalau tidak ada masalah aneh di mana elemen "biarkan sendiri" ), jadi saya sarankan untuk meninggalkannya hanya dengan satu elemen (misalnya, gunakan div di dalam #drop untuk memasukkan semuanya ke dalam)

Inilah jsfiddle yang memecahkan contoh pertanyaan asli (rusak) .

Saya juga telah membuat versi yang disederhanakan bercabang dari contoh @Theodore Brown, tetapi hanya didasarkan pada CSS ini.

Namun, tidak semua browser menerapkan CSS ini: http://caniuse.com/pointer-events

Melihat kode sumber Facebook saya bisa menemukan ini pointer-events: none;beberapa kali, namun mungkin digunakan bersama-sama dengan fallback degradasi anggun. Setidaknya itu sangat sederhana dan menyelesaikan masalah untuk banyak lingkungan.

HD
sumber
Properti pointer-events adalah solusi yang tepat untuk maju, tetapi sayangnya itu tidak berfungsi di IE8-IE10, yang masih banyak digunakan. Juga, saya harus menunjukkan bahwa jsFiddle Anda saat ini bahkan tidak berfungsi di IE11, karena itu tidak menambah pendengar acara yang diperlukan dan pencegahan perilaku default.
Theodore Brown
2
Menggunakan pointer-events memang merupakan jawaban yang baik, saya berjuang sedikit sebelum mencari tahu sendiri, jawaban itu harus lebih tinggi.
floribon
Pilihan yang fantastis bagi kita yang cukup beruntung karena tidak harus mendukung browser lama.
Luke Hansford
7
Bagaimana jika anak-anak Anda adalah Button? oO
David
9
ini hanya berfungsi jika Anda tidak memiliki elemen pengontrol lainnya (edit, hapus) di dalam area tersebut, karena solusi ini memblokir mereka juga ..
Zoltán Süle
45

Di sini, solusi Cross-Browser paling sederhana (serius):

jsfiddle <- coba seret beberapa file di dalam kotak

Anda dapat melakukan sesuatu seperti itu:

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

Dalam beberapa kata, Anda membuat "topeng" di dalam dropzone, dengan lebar & tinggi diwarisi, posisi absolut, yang hanya akan ditampilkan saat dragover dimulai.
Jadi, setelah menunjukkan topeng itu, Anda dapat melakukan trik dengan melampirkan yang lain dragleave & drop peristiwa di atasnya.

Setelah meninggalkan atau menjatuhkan, Anda hanya menyembunyikan topeng lagi.
Sederhana dan tanpa komplikasi.

(Obs .: Saran Greg Pettit - Anda harus yakin bahwa topeng mengarahkan seluruh kotak, termasuk perbatasan)

Diego T. Yamaguchi
sumber
Tidak yakin mengapa, tetapi tidak bekerja secara konsisten dengan Chrome. Terkadang meninggalkan area membuat topeng terlihat.
Greg Pettit
2
Sebenarnya, itu perbatasan. Memiliki topeng yang tumpang tindih perbatasan, tanpa perbatasan sendiri, dan itu harus berfungsi OK.
Greg Pettit
1
Catatan, jsfiddle Anda memiliki bug di dalamnya, di drag_drop, Anda harus menghapus kelas hover di "#box" bukan "# box-a"
entropy
Ini adalah solusi yang bagus, tetapi entah bagaimana itu tidak berhasil untuk saya. Saya akhirnya mencari solusinya. Bagi Anda yang mencari sesuatu yang lain. Anda dapat mencoba ini: github.com/bingjie2680/jquery-draghover
bingjie2680
Tidak. Saya tidak ingin kehilangan kendali atas unsur-unsur anak. Saya telah membuat plugin jQuery sederhana yang berhubungan dengan masalah melakukan pekerjaan untuk kita. Periksa jawabanku .
Luca Fagioli
39

Sudah cukup lama setelah pertanyaan ini diajukan dan banyak solusi (termasuk hacks jelek) disediakan.

Saya berhasil memperbaiki masalah yang sama dengan yang baru-baru ini saya terima berkat jawaban dalam jawaban ini dan saya pikir mungkin bermanfaat bagi seseorang yang membuka halaman ini. Idenya adalah untuk menyimpan evenet.targetdi ondrageentersetiap kali dipanggil pada salah satu elemen orang tua atau anak. Kemudian ondragleaveperiksa apakah target saat ini ( event.target) sama dengan objek tempat Anda menyimpan ondragenter.

Satu-satunya kasus keduanya cocok adalah ketika tarik Anda meninggalkan jendela browser.

Alasan bahwa ini berfungsi dengan baik adalah ketika mouse meninggalkan elemen (katakanlah el1) dan memasuki elemen lain (katakanlah el2), pertama el2.ondragenterdisebut dan kemudian el1.ondragleave. Hanya ketika seret meninggalkan / memasuki jendela browser, event.targetakan berada ''di keduanya el2.ondragenter dan el1.ondragleave.

Ini sampel kerja saya. Saya telah mengujinya di IE9 +, Chrome, Firefox dan 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;
    };
})();

Dan di sini adalah halaman html sederhana:

<!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>

Dengan gaya yang tepat yang telah saya lakukan adalah membuat div bagian dalam (# file-drop-area) jauh lebih besar setiap kali file diseret ke layar sehingga pengguna dapat dengan mudah menjatuhkan file ke tempat yang tepat.

Ali Motevallian
sumber
4
Ini adalah solusi terbaik, lebih baik daripada penghitung (terutama jika Anda mendelegasikan acara) dan bekerja dengan anak-anak yang dapat diseret.
Fred
3
Ini tidak membantu mereka untuk masalah duplikat acara dragenter
BT
Apakah ini berfungsi untuk "anak-anak" yang merupakan elemen pseudo-css atau kelas semu? Saya tidak bisa melakukannya, tapi mungkin saya salah melakukannya.
Josh Bleecher Snyder
1
Pada Maret 2020 saya sudah mendapatkan keberuntungan terbaik dengan solusi ini juga. Catatan: beberapa linter mungkin mengeluh tentang penggunaan ==lebih ===. Anda membandingkan referensi objek target acara, jadi ===berfungsi dengan baik juga.
Alex
33

Cara "benar" untuk menyelesaikan masalah ini adalah untuk menonaktifkan peristiwa pointer pada elemen turunan dari target yang dijatuhkan (seperti dalam jawaban @ HD). Inilah jsFiddle yang saya buat yang menunjukkan teknik ini . Sayangnya, ini tidak berfungsi dalam versi Internet Explorer sebelum IE11, karena mereka tidak mendukung peristiwa pointer .

Untungnya, saya bisa datang dengan solusi yang tidak bekerja di versi lama IE. Pada dasarnya, ini melibatkan mengidentifikasi dan mengabaikan dragleaveperistiwa yang terjadi ketika menyeret elemen anak. Karena dragenteracara dipecat pada simpul anak sebelum dragleaveacara pada induknya, pendengar peristiwa terpisah dapat ditambahkan ke setiap simpul anak yang menambah atau menghapus kelas "abaikan-seret-cuti" dari target yang dijatuhkan. Kemudian dragleavependengar acara target drop hanya dapat mengabaikan panggilan yang terjadi ketika kelas ini ada. Inilah jsFiddle yang menunjukkan solusi ini . Ini diuji dan bekerja di Chrome, Firefox, dan IE8 +.

Memperbarui:

Saya membuat jsFiddle mendemonstrasikan solusi gabungan menggunakan deteksi fitur, di mana peristiwa pointer digunakan jika didukung (saat ini Chrome, Firefox, dan IE11), dan browser kembali ke menambahkan acara ke node anak jika dukungan acara pointer tidak tersedia (IE8 -10).

Theodore Brown
sumber
Jawaban ini menunjukkan solusi untuk penembakan yang tidak diinginkan tetapi mengabaikan pertanyaan "Apakah mungkin untuk mencegah dragleave dari penembakan saat menyeret ke elemen anak?" sepenuhnya.
HD
Perilaku bisa menjadi aneh ketika menyeret file dari luar browser. Di Firefox saya mendapat "Entering child -> Entering Parent -> Leaving child -> Entering child -> Leaving child" tanpa meninggalkan orangtua, yang tersisa dengan kelas "over". IE lama akan membutuhkan pengganti attachEvent untuk addEventListener.
HD
Solusi itu sangat bergantung pada penggelembungan, "false" di semua addEventListener harus ditekankan sebagai hal yang penting (meskipun itu adalah perilaku default), karena banyak orang mungkin tidak tahu tentang itu.
HD
Draggable tunggal itu menambahkan efek pada dropzone yang tidak muncul ketika dropzone digunakan untuk objek yang dapat ditarik lainnya yang tidak memicu peristiwa dragstart. Mungkin menggunakan segala sesuatu sebagai dropzone untuk efek menyeret sambil menjaga dropzone nyata dengan penangan lain akan melakukan itu.
HD
1
@HD Saya memperbarui jawaban saya dengan info tentang cara menggunakan properti CSS pointer-events untuk mencegah dragleaveperistiwa tersebut menembaki Chrome, Firefox, dan IE11 +. Saya juga memperbarui solusi saya yang lain untuk mendukung IE8 dan IE9, selain IE10. Sengaja bahwa efek dropzone hanya ditambahkan ketika menyeret tautan "Seret saya". Orang lain dapat merasa bebas untuk mengubah perilaku ini sebagaimana diperlukan untuk mendukung kasus penggunaannya.
Theodore Brown
14

Masalahnya adalah bahwa dragleaveperistiwa tersebut dipecat ketika mouse berjalan di depan elemen anak.

Saya sudah mencoba berbagai metode pemeriksaan untuk melihat apakah e.targetelemen itu sama dengan thiselemen, tetapi tidak bisa mendapatkan perbaikan.

Cara saya memperbaiki masalah ini sedikit meretas, tetapi bekerja 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
sumber
1
Terima kasih! Namun saya tidak bisa membuatnya bekerja di Chrome. Bisakah Anda memberikan biola yang berfungsi baik pada hack Anda?
pimvdb
3
Saya sedang berpikir untuk melakukannya dengan memeriksa papan juga. Anda melakukan sebagian besar pekerjaan untuk saya, terima kasih :). Saya harus melakukan beberapa penyesuaian:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof
1
Ini tidak akan berfungsi di Chrome karena acara itu tidak memiliki e.xdan e.y.
Hengjie
Saya suka solusi ini. Tidak berfungsi di Firefox terlebih dahulu. Tetapi jika Anda mengganti ex dengan e.clientX dan ey dengan e.clientY berfungsi. Juga berfungsi di Chrome.
Nicole Stutz
Tidak bekerja di chrome untuk saya, juga tidak melakukan apa yang disarankan Chris atau Daniel Stuts
Timo Huovinen
14

jika Anda menggunakan HTML5, Anda bisa mendapatkan clientRect induk:

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

Kemudian di parent.dragleave ():

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

ini jsfiddle

azlar
sumber
2
Jawaban yang sangat bagus.
broc.seib
Terima kasih sobat, saya suka pendekatan javascript biasa.
Tim Gerhard
Saat menggunakan pada elemen yang dimiliki border-radius, memindahkan pointer ke sudut akan benar - benar meninggalkan elemen, tetapi kode ini masih berpikir kita berada di dalam (kita telah meninggalkan elemen tetapi kita masih dalam persegi panjang yang terikat). Maka dragleavepengendali acara tidak akan dipanggil sama sekali.
Jay Dadhania
11

Solusi yang sangat sederhana adalah dengan menggunakan pointer-eventsproperti CSS . Cukup tetapkan nilainya ke noneatas dragstart pada setiap elemen anak. Elemen-elemen ini tidak akan memicu peristiwa yang berhubungan dengan mouse lagi, sehingga mereka tidak akan menangkap mouse di atasnya dan dengan demikian tidak akan memicu dragleave pada induknya.

Jangan lupa untuk mengatur properti ini kembali autosaat menyelesaikan drag;)

FruityFred
sumber
11

Solusi yang cukup sederhana ini bekerja untuk saya sejauh ini, dengan anggapan acara Anda melekat pada masing-masing elemen seret secara terpisah.

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}
Kenneth Spencer
sumber
Ini bagus! terima kasih telah berbagi @kenneth, Anda menyelamatkan hari saya! Banyak jawaban lain hanya berlaku jika Anda memiliki satu zona droppable.
antoni
Bagi saya, ini berfungsi di Chrome dan Firefox, tetapi id tidak berfungsi di Edge. Silakan lihat: jsfiddle.net/iwiss/t9pv24jo
iwis
8

Solusi yang sangat sederhana:

parent.addEventListener('dragleave', function(evt) {
    if (!parent.contains(evt.relatedTarget)) {
        // Here it is only dragleave on the parent
    }
}
Owen M
sumber
Ini sepertinya tidak berfungsi di Safari 12.1: jsfiddle.net/6d0qc87m
Adam Taylor
7

Anda dapat memperbaikinya di Firefox dengan sedikit inspirasi dari kode sumber 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');
        }
    }

}

Sayangnya itu tidak berfungsi di Chrome karena relatedTargettampaknya tidak ada pada dragleaveacara, dan saya menganggap Anda bekerja di Chrome karena contoh Anda tidak berfungsi di Firefox. Ini adalah versi dengan kode di atas diimplementasikan.

robertc
sumber
Terima kasih banyak, tetapi memang itu Chrome, saya mencoba menyelesaikan masalah ini.
pimvdb
5
@ pimvdb Saya melihat Anda telah login bug , saya hanya akan meninggalkan referensi di sini kalau-kalau ada orang lain yang menemukan jawaban ini.
robertc
Memang saya lakukan, tetapi saya lupa menambahkan tautan ke sini. Terima kasih sudah melakukannya.
pimvdb
Bug Chrome telah diperbaiki untuk sementara waktu.
phk
7

Dan ini dia, solusi untuk 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
sumber
Apakah masuk ke «if (typeof event.clientX === 'undefined')»?
Aldekein
1
Berfungsi dengan baik tetapi mungkin ada jendela lain di browser, jadi mendapatkan lokasi mouse dan membandingkannya dengan area layar persegi panjang tidak cukup.
HD
Saya setuju dengan @HD Juga, ini akan menyebabkan masalah ketika elemen memiliki besar border-radiusseperti yang saya jelaskan di komentar saya pada jawaban @ azlar di atas.
Jay Dadhania
7

Berikut solusi lain menggunakan 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');
   }
}

Semoga ini berhasil, ini biola .

marcelj
sumber
3
Ini tidak berhasil bagi saya di luar kotak. Saya perlu menggunakan event.clientX, event.clientYsebagai gantinya, karena mereka relatif terhadap viewport dan bukan halaman. Saya harap itu membantu jiwa yang hilang di luar sana.
Jon Abrams
7

Solusi sederhana adalah menambahkan aturan css pointer-events: noneke komponen anak untuk mencegah pemicu ondragleave. Lihat contoh:

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
sumber
Saya memiliki masalah yang sama dan solusi Anda berfungsi dengan baik. Kiat untuk orang lain, ini adalah kasus saya: jika elemen anak yang Anda coba abaikan acara seret adalah klon dari elemen yang diseret (mencoba mencapai pratinjau visual), Anda dapat menggunakan ini di dalam dragstart ketika membuat klon :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche
4

Tidak yakin apakah ini lintas browser, tetapi saya menguji di Chrome dan itu memecahkan masalah saya:

Saya ingin menarik dan melepas file ke seluruh halaman, tetapi dragleave saya diaktifkan ketika saya menyeret elemen anak. Perbaikan saya adalah dengan melihat x dan y dari mouse:

saya memiliki div yang menutupi seluruh halaman saya, ketika halaman memuat saya menyembunyikannya.

ketika Anda seret dokumen saya tunjukkan, dan ketika Anda menjatuhkan orang tua itu menangani itu, dan ketika Anda meninggalkan orang tua saya periksa x dan y.

$('#draganddrop-wrapper').hide();

$(document).bind('dragenter', function(event) {
    $('#draganddrop-wrapper').fadeIn(500);
    return false;
});

$("#draganddrop-wrapper").bind('dragover', function(event) {
    return false;
}).bind('dragleave', function(event) {
    if( window.event.pageX == 0 || window.event.pageY == 0 ) {
        $(this).fadeOut(500);
        return false;
    }
}).bind('drop', function(event) {
    handleDrop(event);

    $(this).fadeOut(500);
    return false;
});
chrisallick
sumber
1
Retas tapi pintar. Saya suka. Bekerja untukku.
cupcakekid
1
Oh, betapa aku berharap jawaban ini mendapat lebih banyak suara! Terima kasih
Kirby
4

Saya telah menulis perpustakaan kecil bernama Dragster untuk menangani masalah ini, bekerja di mana-mana kecuali diam-diam tidak melakukan apa pun di IE (yang tidak mendukung DOM Event Constructor, tetapi akan sangat mudah untuk menulis sesuatu yang serupa menggunakan acara khusus jQuery)

Ben
sumber
Sangat berguna (setidaknya bagi saya di mana saya hanya peduli dengan Chrome).
M Katz
4

Saya mengalami masalah yang sama dan mencoba menggunakan solusi pk7s. Ini berhasil tetapi bisa dilakukan sedikit lebih baik tanpa elemen dom tambahan.

Pada dasarnya idenya sama - tambahkan hamparan tak terlihat ekstra di atas area droppable. Hanya lakukan ini tanpa elemen dom tambahan. Inilah bagian yang elemen-elemen pseudo-CSS datang untuk dimainkan.

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

Aturan setelah ini akan membuat overlay yang tertutup sepenuhnya untuk area yang dapat dijatuhkan.

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

Inilah solusi lengkapnya: http://jsfiddle.net/F6GDq/8/

Saya harap ini membantu siapa pun dengan masalah yang sama.

rasmusx
sumber
1
Terkadang tidak bekerja pada chrome (tidak menangkap dragleave dengan benar)
Timo Huovinen
4

Cukup periksa apakah elemen yang diseret adalah anak, jika ya, maka jangan hapus kelas gaya 'dragover' Anda. Cukup sederhana dan berfungsi untuk saya:

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })
sofarsoghood
sumber
Sepertinya solusi paling sederhana bagi saya, dan itu memecahkan masalah saya. Terima kasih!
Francesco Marchetti-Stasi
3

Saya telah menemukan masalah yang sama dan inilah solusi saya - yang saya pikir jauh lebih mudah daripada di atas. Saya tidak yakin apakah itu crossbrowser (mungkin bergantung pada urutan genap)

Saya akan menggunakan jQuery untuk kesederhanaan, tetapi solusi harus kerangka kerja yang independen.

Acara ini meletup ke orang tua dengan cara demikian:

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

Kami melampirkan acara

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

Dan itu saja. :) ini berfungsi karena meskipun onEnter on child fires sebelum onLeave on parent, kami menunda sedikit membalikkan urutan, sehingga kelas dihapus terlebih dahulu kemudian diterapkan kembali setelah satu milidetik.

Marcin Raczkowski
sumber
Satu-satunya hal yang dilakukan potongan ini adalah mencegah kelas 'melayang' untuk dihapus dengan menerapkannya kembali pada siklus tick berikutnya (membuat acara 'dragleave' menjadi tidak berguna).
null
Itu tidak berguna. Jika Anda meninggalkan orang tua, itu akan berfungsi seperti yang diharapkan. Kekuatan dari solusi ini adalah kesederhanaannya, itu tidak ideal atau yang terbaik ada. Solusi yang lebih baik adalah menandai entri pada anak, di cek online jika kita baru saja memasukkan anak, dan jika demikian tidak memicu acara cuti. Hovever akan membutuhkan pengujian, penjaga tambahan, checing untuk cucu, dll.
Marcin Raczkowski
3

Saya menulis modul drag-and-drop yang disebut drip-drop yang memperbaiki perilaku aneh ini, antara lain. Jika Anda mencari modul seret-dan-jatuhkan tingkat rendah yang baik yang dapat Anda gunakan sebagai dasar untuk apa saja (unggah file, seret-dan-lepas dalam aplikasi, seret dari atau ke sumber eksternal), Anda harus memeriksa ini modul keluar:

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

Ini adalah bagaimana Anda akan melakukan apa yang Anda coba lakukan di tetes-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')
    }
  })
})

Untuk melakukan ini tanpa pustaka, teknik penghitung adalah apa yang saya gunakan dalam drip-drop, sehingga jawaban dengan nilai tertinggi melewatkan langkah-langkah penting yang akan menyebabkan hal-hal rusak untuk semuanya kecuali drop pertama. Inilah cara melakukannya dengan benar:

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
sumber
2

Solusi kerja alternatif, sedikit lebih sederhana.

//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
sumber
Tampaknya ini tidak selalu berfungsi di Chrome. Kadang-kadang saya akan menerima di clientXatas 0 ketika mouse berada di luar kotak. Memang, elemen saya adalahposition:absolute
Hengjie
Apakah itu terjadi sepanjang waktu atau hanya kadang-kadang? Karena jika mouse bergerak terlalu cepat (ig. Di luar jendela), Anda mungkin mendapatkan nilai yang salah.
Untung
Itu terjadi 90% dari waktu. Ada beberapa kasus yang jarang terjadi (1 dari 10 kali) di mana saya dapat mencapai 0. Saya akan mencoba lagi menggerakkan mouse lebih lambat, tetapi saya tidak bisa mengatakan saya bergerak cepat (mungkin apa yang Anda sebut kecepatan normal).
Hengjie
2

Setelah menghabiskan waktu berjam-jam, saya mendapat saran yang bekerja persis seperti yang dimaksudkan. Saya ingin memberikan isyarat hanya ketika file diseret, dan mendokumentasikan dragover, dragleave menyebabkan flicker yang menyakitkan di browser Chrome.

Ini adalah bagaimana saya menyelesaikannya, juga memberikan isyarat yang tepat bagi pengguna.

$(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
});
kunjungan b
sumber
1

Saya memiliki masalah yang sama - kode saya untuk menyembunyikan dropzone pada acara dragleave untuk tubuh dipecat secara kontroversial ketika melayang-layang elemen anak yang membuat dropzone berkedip di Google Chrome.

Saya bisa menyelesaikan ini dengan menjadwalkan fungsi untuk menyembunyikan dropzone alih-alih memanggilnya segera. Kemudian, jika dragover atau dragleave lain dipecat, panggilan fungsi yang dijadwalkan dibatalkan.

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);
Arseny
sumber
Inilah yang akhirnya saya lakukan juga, tetapi masih samar.
mpen
1

" Dragleave " acara dipecat ketika pointer mouse keluar daerah menyeret wadah sasaran.

Yang membuat banyak akal karena dalam banyak kasus hanya orang tua yang mungkin droppable dan bukan keturunan. Saya pikir event.stopPropogation () seharusnya menangani kasus ini tetapi sepertinya tidak berhasil.

Di atas disebutkan beberapa solusi tampaknya bekerja untuk sebagian besar kasus, tetapi gagal dalam kasus anak-anak yang tidak mendukung acara dragenter / dragleave , seperti iframe .

1 solusinya adalah untuk memeriksa event.relatedTarget dan memverifikasi jika itu berada di dalam wadah kemudian abaikan acara dragleave seperti yang saya lakukan di sini:

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

Anda dapat menemukan biola yang berfungsi di sini !

Lalit Nankani
sumber
1

Terpecahkan ..!

Deklarasikan sembarang array untuk contoh:

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

Tidak perlu khawatir tentang elemen anak.

Shubham Rajodiya
sumber
1

Saya berjuang BANYAK dengan ini, bahkan setelah membaca semua jawaban ini, dan berpikir saya mungkin berbagi solusi dengan Anda, karena saya pikir itu mungkin salah satu pendekatan yang lebih sederhana, meskipun agak berbeda. Pikiranku hanya menghilangkan dragleavependengar acara sepenuhnya, dan mengkode perilaku dragleave dengan setiap acara dragenter baru dipecat, sambil memastikan bahwa acara dragenter tidak akan dipecat secara tidak perlu.

Dalam contoh saya di bawah ini, saya memiliki tabel, di mana saya ingin dapat bertukar konten baris tabel satu sama lain melalui drag & drop API. Pada dragenter, kelas CSS akan ditambahkan ke elemen baris di mana Anda saat ini menyeret elemen Anda, untuk menyorotnya, dan pada dragleave, kelas ini akan dihapus.

Contoh:

Tabel HTML yang sangat mendasar:

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

Dan fungsi dragenter event handler, ditambahkan ke setiap sel tabel (samping dragstart, dragover, drop, dan dragendpenangan, yang tidak spesifik untuk pertanyaan ini, sehingga tidak disalin di sini):

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

Komentar yang sangat mendasar ditinggalkan dalam kode, sehingga kode ini cocok untuk pemula juga. Semoga ini bisa membantu Anda! Perhatikan bahwa Anda tentu saja perlu menambahkan semua pendengar acara yang saya sebutkan di atas ke setiap sel tabel agar ini berfungsi.

DevelJoe
sumber
0

Berikut ini adalah pendekatan lain berdasarkan waktu kejadian.

The dragenterevent dikirim dari elemen anak dapat ditangkap oleh elemen induk dan selalu terjadi sebelum dragleave. Pengaturan waktu antara kedua peristiwa ini benar-benar singkat, lebih pendek dari semua aksi mouse manusia yang mungkin. Jadi, idenya adalah untuk menghafal saat ketika dragenterterjadi dan menyaring dragleaveperistiwa yang terjadi "tidak terlalu cepat" setelah ...

Contoh singkat ini berfungsi di Chrome dan 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
sumber
0

pimvdb ..

Mengapa Anda tidak mencoba menggunakan drop daripada dragleave . Ini berhasil untuk saya. Semoga ini bisa menyelesaikan masalah Anda.

Silakan periksa 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
sumber
2
Jika pengguna berubah pikiran dan menarik file keluar dari browser, ini akan tetap merah.
ZachB
0

Anda dapat menggunakan batas waktu dengan transitioningbendera dan mendengarkan elemen paling atas. dragenter/ dragleavedari acara anak-anak akan meluap ke wadah.

Karena dragenterpada elemen anak diaktifkan sebelum dragleavewadah, kami akan mengatur acara bendera sebagai transisi untuk 1 ms ... dragleavependengar akan memeriksa bendera sebelum 1 ms naik.

Bendera hanya akan benar selama transisi ke elemen anak, dan tidak akan benar ketika beralih ke elemen induk (dari wadah)

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
sumber
0

Anda harus menghapus peristiwa pointer untuk semua objek anak dari target seret.

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
sumber