Bir alt öğeyi gezdirirken HTML5 sürükle ve bırak özelliği tetiklendi


302

Yaşadığım sorun dragleave, bir öğenin alt öğesini gezdirirken bir öğenin olayının başlatılmasıdır . Ayrıca, dragenterana eleman tekrar geri getirildiğinde tetiklenmez.

Basitleştirilmiş bir keman yaptım: 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>

aşağıdaki JavaScript ile:

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

Yapması gereken divşey, orada bir şey sürüklerken damlayı kırmızı yaparak kullanıcıya bildirmektir . Bu işe yarar, ancak pçocuğa sürüklerseniz dragleave, işten çıkarılır ve divartık kırmızı olmaz. Damlaya geri dönmek divde onu tekrar kırmızı yapmaz. Damladan tamamen çıkmak divve kırmızı yapmak için tekrar içine sürüklemek gerekir.

dragleaveBir alt öğeye sürüklerken ateş etmeyi önlemek mümkün müdür ?

2017 Güncellemesi: TL; DR, CSS'yi pointer-events: none;modern tarayıcılarda ve IE11'de çalışan @ HD'nin cevabında açıklandığı gibi arayın.


Bildirilen hata pimvdb hala Mayıs 2012 itibariyle Webkit'te var. Ben de dragover bir sınıf ekleyerek karşı koydum, bu çok sık ateş beri güzel bir yere yakın değil, ama sorunu biraz yama gibi görünüyor.
ajm

2
@ajm: Teşekkürler, bu bir ölçüde işe yarıyor. Bununla birlikte, Chrome'da, muhtemelen dragleavebu durumda hala ateşlendiği için alt öğeye girerken veya ayrılırken bir flaş vardır .
pimvdb

Bir jQuery UI hata upvotes açığız açıldı , bu yüzden kaynak koymaya karar verebilirler
fguillen

@fguillen: Üzgünüm ama bunun jQuery UI ile bir ilgisi yok. Aslında, jQuery'nin hatayı tetiklemesi bile gerekmez. Zaten bir WebKit hatası dosyaladım ancak şu an için güncelleme yok.
pimvdb

2
@pimvdb HD'nin cevabını neden kabul etmiyorsunuz?
TylerH

Yanıtlar:


339

Sadece bir referans sayacı tutmanız, bir dragenter aldığınızda artırmanız, bir dragleave aldığınızda azalmanız gerekir. Sayaç 0 olduğunda - sınıfı kaldırın.

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

Not: drop olayında sayacı sıfırlayın ve eklenen sınıfı temizleyin.

Burada çalıştırabilirsin


8
OMG bu en bariz çözüm ve sadece BİR oyu vardı ... Hadi insanlar, daha iyisini yapabilirsin. Bunu düşünüyordum ama ilk birkaç cevabın çok yönlülüğünü gördükten sonra neredeyse atmıştım. Herhangi bir dezavantajınız var mı?
Arthur Corenzan

11
Sürüklenen öğenin kenarı başka bir sürüklenebilir öğenin kenarına değdiğinde bu işe yaramadı. Örneğin sıralanabilir bir liste; öğeyi aşağıya doğru sürüklemek, bir sonraki sürüklenebilir öğenin üzerine sayacı 0'a düşürmez, bunun yerine 1'de sıkışır. Ancak, onu herhangi bir sürüklenebilir öğenin dışına sürüklersem çalışır. pointer-events: noneÇocuklarla birlikte gittim . Sürüklemeye başladığımda, bu özelliğe sahip bir sınıfa ekliyorum, sürükle bittiğinde sınıfı kaldırıyorum. Safari ve Chrome'da güzel çalıştı, ancak Firefox'ta değil.
Arthur Corenzan

13
Bu benim için tüm tarayıcılarda çalıştı, ancak Firefox. Benim durumumda her yerde çalışan yeni bir çözümüm var. İlk olarak dragenterI kurtarmak event.currentTargetyeni değişkende dragEnterTarget. dragEnterTargetBelirlendiği sürece , başka dragenterolayları görmezden gelirim , çünkü onlar çocuklardan. Bütün dragleaveolaylarda kontrol ederim dragEnterTarget === event.target. Bu yanlışsa, olay bir çocuk tarafından tetiklendiği için yok sayılır. Eğer bu doğruysa ben sıfırlamak dragEnterTargetiçin undefined.
Pipo

3
Harika bir çözüm. Düşmeyi kabul eden işlevde sayacı sıfırlamak zorunda kaldım, aksi takdirde sonraki sürüklemeler beklendiği gibi çalışmadı.
Liam Johnston

2
@Woody Buradaki yorumların büyük listesinde yorumunu görmedim, ama evet, düzeltme bu. Neden cevabınıza dahil etmiyorsunuz?
BT

93

Bir alt öğeye sürüklerken sürükle yapraklarının ateşlenmesini önlemek mümkün müdür?

Evet.

#drop * {pointer-events: none;}

Bu CSS, Chrome için yeterli görünüyor.

Firefox ile kullanırken, #drop'un doğrudan metin düğümleri olmamalıdır (aksi takdirde bir öğenin "kendisine bıraktığı" garip bir sorun vardır ), bu yüzden sadece bir öğeyle bırakmanızı öneririm (örneğin, içinde bir div kullanın #drop her şeyi içine koymak)

İşte orijinal soru (kırık) örneğini çözen bir jsfiddle .

Ben de basitleştirilmiş bir versiyon yaptım @Theodore Brown örneğinden çatallanmış , ancak sadece bu CSS'ye dayanıyorum.

Bununla birlikte, tüm tarayıcılarda bu CSS uygulanmamıştır: http://caniuse.com/pointer-events

Facebook kaynak kodunu görünce bunu pointer-events: none;birkaç kez bulabilirdim , ancak muhtemelen zarif bozulma yedekleriyle birlikte kullanılır. En azından bu kadar basit ve birçok ortam için sorunu çözüyor.


Pointer-events özelliği ileride doğru çözümdür, ancak maalesef hala yaygın olarak kullanılan IE8-IE10'da çalışmaz. Ayrıca, gerekli olay dinleyicileri ve varsayılan davranış önleme eklemediği için geçerli jsFiddle IE11'de bile çalışmadığını belirtmeliyim.
Theodore Brown

2
İşaretçi olaylarını kullanmak gerçekten iyi bir cevap, kendim bulmadan önce biraz mücadele ettim, bu cevap daha yüksek olmalı.
floribon

Eski tarayıcıları desteklemek zorunda kalmayacak kadar şanslı olanlar için harika bir seçenek.
Luke Hansford

7
Çocuklarınız bir Düğme ise ne olur? oo
David

9
yalnızca alanın içinde başka denetleyici öğeleriniz (düzenleme, silme) yoksa çalışır, çünkü bu çözüm onları da engeller ..
Zoltán Süle

45

Burada, en basit Çapraz Tarayıcı çözümü (ciddi olarak):

jsfiddle <- kutunun içinde bir dosya sürüklemeyi deneyin

Böyle bir şey yapabilirsiniz:

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

Birkaç kelimeyle, dropzone içinde, genişlik ve yükseklik devralınmış, mutlak konum ile bir ejderha maskesi oluşturursunuz.
Bu maskeyi gösterdikten sonra, diğer dragleave & drop olaylarını ekleyerek hile yapabilirsiniz.

Ayrıldıktan veya düştükten sonra maskeyi tekrar gizlersiniz.
Basit ve sorunsuz.

(Obs .: Greg Pettit tavsiyesi - Maskenin kenarlık da dahil olmak üzere tüm kutunun üzerine geldiğinden emin olmalısınız)


Neden olduğundan emin değilim, ancak Chrome ile tutarlı bir şekilde çalışmıyor. Bazen alandan ayrılmak maskeyi görünür kılar.
Greg Pettit

2
Aslında bu sınır. Maskenin kendi kenarlığı olmadan kenarlıkla çakışmasını sağlayın ve düzgün çalışması gerekir.
Greg Pettit

1
Not, jsfiddle'ınızın içinde bir hata var, drag_drop'ta, "#box" değil "#box" üzerindeki vurgulu sınıfı kaldırmalısınız
entropi

Bu güzel bir çözüm, ama bir şekilde benim için işe yaramadı. Sonunda bir çözüm buldum. Başka bir şey arayanlar için. Bunu deneyebilirsin: github.com/bingjie2680/jquery-draghover
bingjie2680

Hayır. Alt öğeler üzerindeki kontrolü kaybetmek istemiyorum. Bizim için işi yapan sorunla ilgilenen basit bir jQuery eklentisi yaptım. Cevabımı kontrol et .
Luca Fagioli

39

Bu soru sorulduktan sonra oldukça zaman geçti ve çok sayıda çözüm (çirkin hack'ler dahil) sağlandı.

Bu cevabın cevabı sayesinde son zamanlarda yaşadığım sorunu çözmeyi başardım ve bu sayfaya gelen birine yardımcı olabileceğini düşündüm. Bütün fikir saklamaktır evenet.targetiçinde ondrageenterebeveyn veya alt öğelerinden herhangi biri üzerinde her şey denir. Ardından ondragleave, geçerli hedefin ( event.target) içinde sakladığınız nesneye eşit olup olmadığını kontrol edinondragenter .

Bu ikisinin eşleştiği tek durum, sürüklemenizin tarayıcı penceresinden çıkmasıdır.

Bunun iyi çalışmasının nedeni, farenin bir öğeden ayrılması (diyelim el1) ve başka bir öğeye (diyelim ) girmesidir el2, önce el2.ondragenterdenir ve sonra el1.ondragleave. Yalnızca sürükleme tarayıcı penceresinden çıkarken / girerken, hem ve 'de event.targetolacaktır .''el2.ondragenterel1.ondragleave

İşte benim çalışma örneğim. IE9 +, Chrome, Firefox ve Safari'de test ettim.

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

İşte basit bir html sayfası:

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

Düzgün şekillendirme ile yaptığım şey, bir dosya ekrana sürüklendiğinde iç div (# dosya bırakma alanı) çok daha büyük yapmaktır, böylece kullanıcı dosyaları uygun yere kolayca bırakabilir.


4
Bu en iyi çözümdür, sayaçtan daha iyidir (özellikle olayları devrediyorsanız) ve sürüklenebilir çocuklarla da çalışır.
Fred

3
Bu yinelenen dragenter olayları sorunu için tho yardımcı olmuyor
BT

Bu css sözde elemanlar veya sözde sınıflar olan "çocuklar" için çalışır mı? Anlayamadım, ama belki de yanlış yapıyordum.
Josh Bleecher Snyder

1
Mart 2020 itibariyle bu çözümde de en iyi şansı yakaladım. Not: Bazı linterler ==aşırı kullanımdan şikayet edebilir ===. Olay hedefi nesne referanslarını karşılaştırıyorsunuz, bu yüzden ===de iyi çalışıyor.
Alex

33

Bu sorunu çözmenin "doğru" yolu, bırakma hedefinin alt öğelerindeki işaretçi olaylarını devre dışı bırakmaktır (@ HD'nin yanıtında olduğu gibi). İşte bu tekniği gösteren bir jsFiddle . Ne yazık ki, bu işaretçi olaylarını desteklemedikleri için IE11'den önceki Internet Explorer sürümlerinde çalışmaz .

Neyse ki, geçici bir çözüm ile gelip başardı yapar IE eski sürümlerinde çalışması. Temel olarak, dragleavealt öğelerin üzerine sürüklerken meydana gelen olayların tanımlanmasını ve yoksayılmasını içerir . Çünkü dragenterolay öncesinde çocuk düğümler üzerinde ateşlenir dragleaveebeveyn üzerinde etkinliğe ayrı olay dinleyicileri eklemek veya bırakma hedeften bir "göz ardı-sürükle izni" sınıfını kaldırmak her çocuk düğüme eklenebilir. Ardından bırakma hedefinin dragleaveolay dinleyicisi, bu sınıf mevcut olduğunda gerçekleşen çağrıları yok sayabilir. İşte bu geçici çözümü gösteren bir jsFiddle . Chrome, Firefox ve IE8 + 'da test edilmiş ve çalışmaktadır.

Güncelleme:

Özellik algılamayı kullanarak birleşik bir çözüm gösteren bir jsFiddle oluşturdum , burada işaretçi olayları destekleniyorsa (şu anda Chrome, Firefox ve IE11) ve işaretçi olay desteği mevcut değilse tarayıcı alt düğümlere etkinlik eklemeye geri dönüyor (IE8 -10).


Bu yanıt istenmeyen ateşleme için bir geçici çözüm gösterir, ancak "Bir alt öğeye sürüklerken sürükle yapraklarının ateşlenmesini önlemek mümkün mü?" Baştan sona.
HD

Tarayıcı dışından bir dosyayı sürüklerken davranış garipleşebilir. Firefox'ta, "over" sınıfından ayrılan ebeveyni terk etmeden "Entering child -> Ebeveyn Girme -> Leaving child -> Child girme -> Leaving child" var. Eski IE'nin addEventListener için bir attachEvent değiştirilmesi gerekir.
HD

Bu çözüm, köpürmeye bağlıdır, birçok addEventListener'daki "false" (temel davranış olmasına rağmen) zorunlu olarak vurgulanmalıdır, çünkü birçok kişi bunu bilmeyebilir.
HD

Bu tek sürüklenebilir, dropzone'a, dragstart olayını tetiklemeyen diğer sürüklenebilir nesneler için dropzone kullanıldığında görünmeyen bir efekt ekler. Belki de gerçek dropzone'u diğer işleyicilerle tutarken sürükleme etkisi için her şeyi bir dropzone olarak kullanmak bunu yapardı.
HD

1
@HD Yanıtın, dragleaveetkinliğin Chrome, Firefox ve IE11 + 'da tetiklenmesini önlemek için işaretçi etkinlikleri CSS özelliğini kullanma hakkındaki bilgilerle güncelledim . IE10'a ek olarak IE8 ve IE9'u desteklemek için diğer geçici çözümümü de güncelledim. Dropzone efektinin yalnızca "Beni sürükleyin" bağlantısı sürüklenirken eklenmesi kasıtlıdır. Diğerleri, kullanım durumlarını desteklemek için gerektiğinde bu davranışı değiştirebilir.
Theodore Brown

14

Sorun, dragleavefare alt öğenin önüne gittiğinde olayın başlatılmasıdır.

e.targetÖğenin öğe ile aynı olup olmadığını kontrol etmek için çeşitli yöntemler denedim this, ancak herhangi bir gelişme elde edemedim.

Bu sorunu çözme şeklim bir parça hackti, ama% 100 çalışıyor.

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
Teşekkürler! Ancak bunun Chrome'da çalışmasını sağlayamıyorum. Kesmek için çalışan bir keman sağlayabilir misiniz?
pimvdb

3
Bunu ben de koordinatları kontrol ederek yapmayı düşünüyordum. İşin çoğunu benim için yaptın, teşekkürler :). Yine de bazı ayarlamalar yapmak zorunda kaldım:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof

1
Chrome'da çalışmaz, çünkü etkinlikte e.xve yoktur e.y.
Hengjie

Bu çözümü seviyorum. İlk olarak Firefox'ta çalışmadı. Ancak, ex.clientX ve ey e.clientY ile değiştirirseniz çalışır. Chrome'da da çalışır.
Nicole Stutz

Benim için krom işe yaramadı ne yaptım Chris veya Daniel Stuts önerdi
Timo Huovinen

14

HTML5 kullanıyorsanız, ebeveynin istemcisini alabilirsiniz

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

Sonra parent.dragleave () içinde:

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

işte bir jsfiddle


2
Mükemmel cevap.
broc.seib

Teşekkürler dostum, düz javascript yaklaşımını seviyorum.
Tim Gerhard

Sahip olan öğeleri kullanırken border-radius, işaretçiyi köşeye yakın hareket ettirmek aslında öğeyi terk eder, ancak bu kod hala içeride olduğumuzu düşünür (öğeyi bıraktık, ancak yine de sınırlayıcı dikdörtgenin içindeyiz). O zaman dragleaveolay giderici hiç çağrılmaz.
Jay Dadhania

11

Çok basit bir çözüm pointer-eventsCSS özelliğini kullanmaktır . Sadece onun değerini noneüzerine DragStart her çocuk elemana. Bu öğeler artık fare ile ilgili olayları tetiklemeyecek, bu nedenle fareyi üzerlerinde yakalamayacak ve böylece sürükleme yapraklarını tetiklemeyecek üst üzerinde .

autoSürüklemeyi bitirirken bu özelliği tekrar ayarlamayı unutmayın ;)


11

Bu oldukça basit çözüm, etkinliğinizin her sürükleme öğesine ayrı ayrı eklendiğini varsayarak benim için şimdiye kadar çalışıyor.

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

Bu harika! @kenneth paylaştığın için teşekkürler, günümü kurtardın! Diğer cevapların çoğu sadece tek bir bırakılabilir bölgeniz varsa geçerlidir.
antoni

Benim için Chrome ve Firefox'ta çalışıyor, ancak id Edge'de çalışmıyor. Lütfen bakınız: jsfiddle.net/iwiss/t9pv24jo
iwis

9

Çok basit bir çözüm:

parent.addEventListener('dragleave', function(evt) {
    if (!parent.contains(evt.relatedTarget)) {
        // Here it is only dragleave on the parent
    }
}


7

Firefox'ta jQuery kaynak kodundan biraz ilham alarak düzeltebilirsiniz :

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

    if (related !== this) {

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

        if (!inside) {

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

}

Maalesef Chrome'da çalışmıyor, çünkü etkinliklerde relatedTargetyok gibi görünüyor dragleaveve örneğin Firefox'ta çalışmadığı için Chrome'da çalıştığınızı varsayıyorum. Yukarıdaki kodun uygulandığı bir sürüm .


Çok teşekkürler, ama gerçekten Chrome bu sorunu çözmeye çalışıyorum.
pimvdb

5
@pimvdb Bir hata kaydolduğunuzu görüyorum , başka birinin bu yanıta rastlaması durumunda buraya bir referans bırakacağım.
robertc

Gerçekten yaptım, ama buraya bir bağlantı eklemeyi unuttum. Bunu yaptığınız için teşekkürler.
pimvdb

Chrome hatası bu süre içinde düzeltildi.
phk

7

Ve işte gidiyor, Chrome için bir çözüm:

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

«İf (typeof event.clientX === 'tanımsız')» içine giriyor mu?
Aldekein

1
Güzel çalıştı ama tarayıcı üzerinde başka bir pencere olabilir, bu yüzden fare konumunu almak ve dikdörtgen ekran alanı ile karşılaştırmak yeterli değildir.
HD

@HD'ye katılıyorum Ayrıca, border-radius@ azlar'ın yukarıdaki cevabına yaptığım yorumda açıkladığım gibi , eleman büyük olduğunda sorunlara neden olacaktır .
Jay Dadhania

7

İşte document.elementFromPoint kullanan başka bir çözüm:

 dragleave: function(event) {
   var event = event.originalEvent || event;
   var newElement = document.elementFromPoint(event.pageX, event.pageY);
   if (!this.contains(newElement)) {
     $(this).removeClass('red');
   }
}

Umarım bu işe yarar , işte bir keman .


3
Bu benim için kutudan çıkmadı. Bunun event.clientX, event.clientYyerine kullanmam gerekiyordu , çünkü bunlar sayfaya değil görünüm penceresine göre. Umarım bu başka bir kayıp ruha yardımcı olur.
Jon Abrams

7

Basit bir çözüm, pointer-events: nonetetikleyiciyi önlemek için alt bileşene css kuralını eklemektir ondragleave. Örneğe bakın:

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>


Aynı sorunu yaşadım ve çözümünüz harika çalışıyor. Diğerleri için ipucu, bu benim durumumdu: sürükleme olayını yoksaymaya çalıştığınız alt öğe sürüklenen öğenin klonuysa (görsel bir önizleme elde etmeye çalışıyorsanız), klonu oluştururken bunu dragstart içinde kullanabilirsiniz :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche

4

Bu çapraz tarayıcının olup olmadığından emin değilim, ancak Chrome'da test ettim ve sorunumu çözdü:

Bir dosyayı tüm sayfanın üzerine sürükleyip bırakmak istiyorum, ancak alt öğenin üzerine sürüklediğimde sürükleme yaprakım tetikleniyor. Benim düzeltme farenin x ve y bakmak oldu:

tüm sayfa bindirmeleri bir div var, sayfa yüklendiğinde ben gizlemek.

belgenin üzerine sürüklediğinizde bunu gösteririm ve üst öğeye bıraktığınızda işlenir ve üst öğeden ayrıldığınızda x ve y'yi kontrol ederim.

$('#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
Keskin ama akıllı. Severim. Benim için çalıştı.
cupcakekid

1
Ah, bu cevabın nasıl daha fazla oy almasını diliyorum! Teşekkür ederim
Kirby

4

Bu sorunu çözmek için Dragster adlı küçük bir kütüphane yazdım , IE'de hiçbir şey yapmadan (DOM Etkinlik Oluşturucularını desteklemiyor, ancak jQuery'nin özel etkinliklerini kullanarak benzer bir şey yazmak oldukça kolay olurdu) dışında her yerde çalışıyor


Çok yararlı (en azından benim için yalnızca Chrome'u önemsediğim yer).
M Katz

4

Aynı sorunu yaşıyordum ve pk7s çözümünü kullanmaya çalıştım. İşe yaradı, ancak ekstra dom öğeleri olmadan biraz daha iyi yapılabilirdi.

Temel olarak fikir aynı - düşebilir alana ekstra görünmez bir kaplama ekleyin. Bunu sadece ekstra dom öğeleri olmadan yapalım. İşte bölüm CSS sözde elemanlar oynamaya geliyordu.

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

Bu kuraldan sonra, bırakılabilir alan için tamamen kaplanmış bir kaplama oluşturulur.

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

İşte tam çözüm: http://jsfiddle.net/F6GDq/8/

Umarım aynı problemi olan herkese yardımcı olur.


1
Bazen krom üzerinde çalışmaz (dragleave'ı düzgün bir şekilde yakalamaz)
Timo Huovinen

4

Sürüklenen öğenin alt öğe olup olmadığını kontrol edin, eğer öyleyse 'dragover' stil sınıfınızı kaldırmayın. Oldukça basit ve benim için çalışıyor:

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

Benim için en basit çözüm gibi görünüyor ve sorunumu çözdü. Teşekkürler!
Francesco Marchetti-Stasi

3

Aynı soruna rastladım ve işte benim çözümüm - ki bence yukarıda olduğundan çok daha kolay. Crossbrowser olup olmadığından emin değilim (köpürme düzenine bile bağlı olabilir)

Basitlik için jQuery kullanacağım, ancak çözüm çerçeveden bağımsız olmalıdır.

Olay, şu şekilde verildiğinde her iki şekilde de ebeveynlere gönderilir:

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

Etkinlik ekliyoruz

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

Ve bununla ilgili. :) çalışır çünkü onEnter önce çocuk yangınlarına rağmen onLeave ebeveyn üzerinde, biz biraz ters gecikme sipariş, böylece sınıf ilk önce kaldırılır sonra milisaniye sonra tekrar.


Bu pasajın yaptığı tek şey, 'vurgulanan' sınıfın bir sonraki onay döngüsünü yeniden uygulayarak kaldırılmasını önlemektir ('dragleave' olayını işe yaramaz hale getirmek).
null

Yararsız değil. Ebeveynten ayrılırsanız beklendiği gibi çalışır. Bu çözümün gücü sadeliktir, ideal veya en iyisi değildir. Daha iyi bir çözüm, bir çocuğa giriş girmek, sadece çocuğa girip girmediğimizi kontrol etmek ve izin verilmiyorsa izin olayını tetiklemek olacaktır. Teste, ekstra korumaya, torunlara checing'e vb.
İhtiyaç duyacaktır

3

Bu tuhaf davranışı düzelten damla damla adlı bir sürükle bırak modülü yazdım . Herhangi bir şeyin temeli olarak kullanabileceğiniz iyi bir düşük düzey sürükle ve bırak modülü arıyorsanız (dosya yükleme, uygulama içi sürükle ve bırak, dış kaynaklardan veya dış kaynaklara sürükleme) bunu kontrol etmelisiniz. modül çıkışı:

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

Damla damla ile yapmaya çalıştığınız şey şu şekilde olurdu:

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

Bunu bir kütüphane olmadan yapmak için, sayaç tekniği damla damlada kullandığım şeydir, en yüksek puanlı cevap ilk damla hariç her şey için her şeyin kırılmasına neden olacak önemli adımları kaçırır. Düzgün nasıl yapılacağı aşağıda açıklanmıştır:

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

Alternatif bir çalışma çözümü, biraz daha basit.

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

Bu her zaman Chrome'da işe yaramıyor gibi görünüyor. clientXFare kutunun dışındayken zaman zaman 0'ın üzerine çıkardım . Verilmiş, elementlerimposition:absolute
Hengjie

Her zaman mı yoksa sadece bazen mi oluyor? Çünkü fare çok hızlı hareket ediyorsa (pencere dışında), yanlış değerler alabilirsiniz.
Profet

Zamanın% 90'ı oluyor. 0'a ulaştırabileceğim nadir durumlar (10 kez 1) var. Fareyi daha yavaş hareket ettirmeyi tekrar deneyeceğim, ancak hızlı hareket ettiğimi söyleyemedim (belki de normal hız dediğin şey).
Hengjie

2

Çok fazla saat geçirdikten sonra bu öneriyi tam olarak planlandığı gibi çalıştırabildim. Sadece dosyalar sürüklendiğinde bir ipucu vermek istedim ve belge dragover, dragleave Chrome tarayıcısında acı verici titremelere neden oluyordu.

Bu şekilde çözdüm, ayrıca kullanıcı için uygun ipuçlarını atıyorum.

$(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

Benzer bir sorunum vardı - gövde için dragleave olayında dropzone gizleme kodum, Google Chrome'da dropzone titremesi yapan alt öğelerin üzerine geldiğinde sıkı bir şekilde tetiklendi.

Bunu hemen çağırmak yerine dropzone'u gizleme işlevini zamanlayarak çözebildim. Ardından, başka bir ejderha ya da dragleave tetiklenirse, programlanan işlev çağrısı iptal edilir.

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

Ben de bunu yaptım, ama yine de kabataslak.
16:20

1

Fare işaretçisi hedef kabın sürükleme alanından çıktığında " dragleave " olayı tetiklenir.

Hangi sadece ebeveyn olabilir birçok durumda olduğu gibi mantıklı bir çok yapar droppable torunları değil. Event.stopPropogation () düşünüyorum bu davayı ele gerekirdi ama hile yapmaz gibi görünüyor.

Yukarıda belirtilen bazı çözümler çoğu durumda işe yarıyor gibi görünmektedir, ancak iframe gibi dragenter / dragleave olaylarını desteklemeyen çocuklar için başarısız olur .

1 geçici çözümü event.relatedTarget öğesini kontrol etmek ve kabın içinde olup olmadığını doğrulamak ve sonra burada yaptığım gibi dragleave olayını yok saymaktır :

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

Burada çalışan bir keman bulabilirsiniz !


1

Çözüldü ..!

Ex için herhangi bir dizi bildirin:

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

Çocuk unsurları hakkında endişelenmenize gerek yok.


1

Tüm bu cevapları okuduktan sonra bile bununla çok uğraştım ve çözümümü sizinle paylaşabileceğimi düşündüm, çünkü bunun biraz daha basit yaklaşımlardan biri olabileceğini düşündüm. Benim düşüncem, dragleaveolay dinleyicisini tamamen atlamak ve sürülen her yeni dragenter olayı ile dragleave davranışını kodlamak ve dragenter olaylarının gereksiz yere tetiklenmemesini sağlamaktı.

Aşağıdaki örneğimde, sürükleyip bırakma API'si aracılığıyla tablo satırı içeriğini birbiriyle değiştirebilmek istediğim bir tablo var. Açık olarak dragenter, o anda öğenizi sürüklediğiniz satır öğesini bir CSS sınıfı eklenecek, vurgulanacak vedragleave bu sınıf kaldırılacaktır.

Misal:

Çok temel HTML tablosu:

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

Ve her bir tablo hücresinin üzerine eklenen dragenter olay işleyicisi işlevi, (kenara dragstart, dragover, dropve dragendbu soruya özgü olmayan yükleyiciler, yani burada kopyalanan değil):

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

Kodda çok temel yorumlar bırakıldı, böylece bu kod yeni başlayanlar için de uygun. Bu size yardımcı olacağını umuyoruz! Elbette bunun için yukarıda bahsettiğim tüm olay dinleyicilerini her tablo hücresine eklemeniz gerekeceğini unutmayın.


0

İşte olayların zamanlamasına dayanan başka bir yaklaşım.

Alt dragenteröğeden gönderilen olay, üst öğe tarafından yakalanabilir ve her zaman. Öğesinden önce gerçekleşir dragleave. Bu iki olay arasındaki zamanlama, gerçekten olası herhangi bir insan fare hareketinden daha kısadır. Buradaki fikir, gerçekleşen zamanı ezberlemek ve sonrasında "çok hızlı değil" gerçekleşen olayları dragenterfiltrelemek dragleave...

Bu kısa örnek Chrome ve Firefox'ta çalışır:

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

Neden dragleave yerine drop kullanmayı denemiyorsun ? Benim için çalıştı. Umarız bu sorununuzu çözer.

Lütfen jsFiddle'ı kontrol edin: 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
Kullanıcı fikrini değiştirir ve dosyayı tarayıcıdan geri sürüklerse, bu kırmızı renkte kalacaktır.
ZachB

0

transitioningBayrağı olan bir zaman aşımı kullanabilir ve üst öğeyi dinleyebilirsiniz. dragenter/ dragleaveçocuk olaylarından konteyner kadar kabarcık olacak.

Yana dragenterönce alt öğe yangınlarla ilgili dragleavekabın biz 1ms için geçiş olarak bayrak gösterisi ayarlayacaktır ... dragleave1ms önce bayrak kontrol edecektir dinleyici kalmıştır.

Bayrak yalnızca alt öğelere geçişler sırasında doğru olur ve bir üst öğeye (kabın) geçiş yaparken doğru olmaz

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

Sürükleme hedefinin tüm alt nesneleri için işaretçi olaylarını kaldırmanız gerekir.

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) {
                //
            }
        }
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.