'dragleave' de l'élément parent se déclenche lors du glissement sur des éléments enfants

163

Aperçu

J'ai la structure HTML suivante et j'ai attaché les événements dragenteret dragleaveà l' <div id="dropzone">élément.

<div id="dropzone">
    <div id="dropzone-content">
        <div id="drag-n-drop">
            <div class="text">this is some text</div>
            <div class="text">this is a container with text and images</div>
        </div>
    </div>
</div>

Problème

Lorsque je fais glisser un fichier sur le <div id="dropzone">, l' dragenterévénement est déclenché comme prévu. Cependant, lorsque je déplace ma souris sur un élément enfant, tel que <div id="drag-n-drop">, l' dragenterévénement est déclenché pour l' <div id="drag-n-drop">élément, puis l' dragleaveévénement est déclenché pour l' <div id="dropzone">élément.

Si je survole à <div id="dropzone">nouveau l' élément, l' dragenterévénement est à nouveau déclenché, ce qui est cool, mais l' dragleaveévénement est déclenché pour l'élément enfant qui vient de quitter, donc l' removeClassinstruction est exécutée, ce qui n'est pas cool.

Ce comportement est problématique pour 2 raisons:

  1. Je m'attache seulement dragenter& dragleaveau<div id="dropzone"> donc je ne comprends pas pourquoi les éléments enfants ont ces événements attachés aussi bien.

  2. Je traîne toujours sur l' <div id="dropzone">élément tout en survolant ses enfants donc je ne veux dragleavepas tirer!

jsFiddle

Voici un jsFiddle à bricoler: http://jsfiddle.net/yYF3S/2/

Question

Alors ... comment puis-je faire en sorte que lorsque je fais glisser un fichier sur l' <div id="dropzone">élément, dragleavene se déclenche pas même si je traîne sur des éléments enfants ... il ne devrait se déclencher que lorsque je quitte l' <div id="dropzone">élément ... . le survol / le glissement n'importe où dans les limites de l'élément ne doit pas déclencher l' dragleaveévénement.

J'ai besoin que cela soit compatible avec tous les navigateurs, au moins dans les navigateurs prenant en charge le glisser-déposer HTML5, donc cette réponse n'est pas adéquate.

Il semble que Google et Dropbox aient compris cela, mais leur code source est minifié / complexe, donc je n'ai pas été en mesure de comprendre cela à partir de leur implémentation.

Hristo
la source
Empêcher la propagation d'événements viae.stopPropagation();
Blender
Je pense que je suis déjà ... consultez la mise à jour
Hristo
Pouvez-vous publier une démo quelque part sur jsfiddle.net que les gens peuvent bricoler?
Blender
@Blender ... bien sûr, donnez-moi quelques minutes!
Hristo
@Blender ... J'ai mis à jour ma question avec un violon
Hristo

Réponses:

171

Si vous n'avez pas besoin de lier des événements aux éléments enfants, vous pouvez toujours utiliser la propriété pointer-events.

.child-elements {
  pointer-events: none;
}
Ben Rudolph
la source
4
Meilleure solution! Je ne pensais même pas pouvoir résoudre le problème en utilisant le CSS de manière aussi élégante. Merci beaucoup!
serg66
1
Oh oui. Ça y est ...!
mcmlxxxiii
23
Le seul inconvénient de cette approche est qu'elle détruit tous les événements de pointeur sur les éléments enfants (par exemple, ils ne peuvent plus avoir de :hoverstyles séparés ou de gestionnaires d'événements de clic). Si vous souhaitez conserver ces événements, voici une autre solution de contournement que j'ai utilisée: bensmithett.github.io/dragster
Ben
2
Combinez avec le sélecteur d'enfants css pour atteindre tous les enfants de votre dropzone: .dropzone * {pointer-events: none;}
Philipp F
7
Vous utilisez déjà jQuery, donc juste $('.element').addClass('dragging-over')pour l'élément que vous faites glisser, et $('.element').removeClass('dragging-over')lorsque vous faites glisser. Ensuite, dans votre CSS, vous pouvez avoir .element.dragging-over * { pointer-events: none; }. Cela supprime les événements de pointeur de tous les éléments enfants uniquement lorsque vous faites glisser.
Gavin
56

J'ai finalement trouvé une solution qui me satisfait. En fait, j'ai trouvé plusieurs façons de faire ce que je veux mais aucune n'a été aussi réussie que la solution actuelle ... dans une solution, j'ai connu des scintillements fréquents à la suite de l'ajout / suppression d'une bordure à l' #dropzoneélément ... dans une autre, la bordure n'a jamais été supprimé si vous vous éloignez du navigateur.

Quoi qu'il en soit, ma meilleure solution hacky est la suivante:

var dragging = 0;

attachEvent(window, 'dragenter', function(event) {

    dragging++;
    $(dropzone).addClass('drag-n-drop-hover');

    event.stopPropagation();
    event.preventDefault();
    return false;
});

attachEvent(window, 'dragover', function(event) {

    $(dropzone).addClass('drag-n-drop-hover');

    event.stopPropagation();
    event.preventDefault();
    return false;
});

attachEvent(window, 'dragleave', function(event) {

    dragging--;
    if (dragging === 0) {
        $(dropzone).removeClass('drag-n-drop-hover');
    }

    event.stopPropagation();
    event.preventDefault();
    return false;
});

Cela fonctionne plutôt bien mais des problèmes sont survenus dans Firefox car Firefox invoquait deux fois dragenterdonc mon compteur était désactivé. Mais néanmoins, ce n'est pas une solution très élégante.

Ensuite, je suis tombé sur cette question: Comment détecter l'événement dragleave dans Firefox lors du glissement hors de la fenêtre

J'ai donc pris la réponse et l' ai appliquée à ma situation:

$.fn.dndhover = function(options) {

    return this.each(function() {

        var self = $(this);
        var collection = $();

        self.on('dragenter', function(event) {
            if (collection.size() === 0) {
                self.trigger('dndHoverStart');
            }
            collection = collection.add(event.target);
        });

        self.on('dragleave', function(event) {
            /*
             * Firefox 3.6 fires the dragleave event on the previous element
             * before firing dragenter on the next one so we introduce a delay
             */
            setTimeout(function() {
                collection = collection.not(event.target);
                if (collection.size() === 0) {
                    self.trigger('dndHoverEnd');
                }
            }, 1);
        });
    });
};

$('#dropzone').dndhover().on({
    'dndHoverStart': function(event) {

        $('#dropzone').addClass('drag-n-drop-hover');

        event.stopPropagation();
        event.preventDefault();
        return false;
    },
    'dndHoverEnd': function(event) {

        $('#dropzone').removeClass('drag-n-drop-hover');

        event.stopPropagation();
        event.preventDefault();
        return false;
    }
});

C'est propre et élégant et semble fonctionner dans tous les navigateurs que j'ai testés jusqu'à présent (je n'ai pas encore testé IE).

Hristo
la source
C'est la meilleure solution à ce jour, le seul problème est que ce code ne fonctionne pas lorsque vous ajoutez un fichier, le supprimez et faites glisser un autre fichier dessus. Ce n'est qu'après avoir levé HoverEnd et relancé HoverStart que ce code fonctionne à nouveau.
MysticEarth
@MysticEarth pouvez-vous créer un jsFiddle pour une démonstration quand cela ne fonctionne pas?
Hristo
1
Merci pour votre réponse. Cela a été très utile, car l'interface glisser-déposer HTML5 et les écouteurs d'événements peuvent être très pénibles. C'est le meilleur moyen de gérer les événements fous, en particulier lorsque vous souhaitez gérer plusieurs instances d'éléments de zone de dépôt.
Nico O
Pourquoi devons-nous appeler stopPropagation () preventDefault () et retourner false. Le retour faux devrait suffire. Devrait-il ?
Dejo
Il est redondant, mais appelant stopPropagation()et preventDefault()est préféré. Je laisserais tomber le return false.
Peeja
38

C'est un peu moche mais ça marche bon sang! ...

Sur votre gestionnaire 'dragenter', stockez event.target (dans une variable à l'intérieur de votre fermeture, ou autre), puis dans votre gestionnaire 'dragleave' ne lancez votre code que si event.target === celui que vous avez stocké.

Si votre 'dragenter' se déclenche lorsque vous ne le souhaitez pas (c'est-à-dire lorsqu'il entre après avoir quitté des éléments enfants), la dernière fois qu'il se déclenche avant que la souris ne quitte le parent, c'est sur le parent, donc le parent sera toujours le «dragenter» final avant le «dragleave» prévu.

(function () {

    var droppable = $('#droppable'),
        lastenter;

    droppable.on("dragenter", function (event) {
        lastenter = event.target;
        droppable.addClass("drag-over");            
    });

    droppable.on("dragleave", function (event) {
        if (lastenter === event.target) {
            droppable.removeClass("drag-over");
        }
    });

}());
hacklikecrack
la source
Je vais essayer plus tard. Si quoi que ce soit, cela semble plus propre que ma solution, mais je ne peux pas être sûr de sa compatibilité entre les navigateurs simplement en le regardant. Où avez-vous testé cela?
Hristo le
1
uniquement Chrome, et ne peut fonctionner qu'en supposant qu'il existe un espace physique entre les éléments enfants et le conteneur.
hacklikecrack le
5
Ma réponse préférée ici. Ne se sent pas moche du tout :)
Matt Way
1
Fonctionne pour moi dans IE, Chrome, FF
Vicentiu Bacioiu
1
Cela ne fonctionne pas si le glissement commence sur les enfants. Par exemple, faire glisser quelque chose depuis une autre fenêtre d'une manière qui commence à l'intérieur de l'enfant.
FINDarkside
29

Au début, j'étais d'accord avec les gens qui rejetaient l' pointer-events: noneapproche. Mais ensuite je me suis demandé:

Avez-vous vraiment besoin d'événements de pointeur pour travailler sur les éléments enfants pendant que le glissement est en cours ?

Dans mon cas, j'ai beaucoup de choses en cours dans les enfants, par exemple, survoler pour afficher des boutons pour des actions supplémentaires, l'édition en ligne, etc. Cependant, rien de tout cela n'est nécessaire ou même souhaité lors d' un glisser.

Dans mon cas, j'utilise quelque chose comme ceci pour désactiver les événements de pointeur de manière sélective pour tous les nœuds enfants du conteneur parent:

  div.drag-target-parent-container.dragging-in-progress * {
    pointer-events: none;
  }

Utilisez votre approche préférée pour ajouter / supprimer la classe dragging-in-progressdans les gestionnaires dragEnter/ dragLeaveevent, comme je l'ai fait ou faites de même dans dragStart, et. Al.

bargar
la source
1
Cette solution est à la fois la plus simple et est (pour autant que je sache) totalement à l'épreuve. Ajoutez simplement la classe à l' dragstartévénement et supprimez-la dragend.
cmann
C'est une solution plutôt sympa mais pas parfaite. Si le glissement commence sur l'élément enfant (glissement depuis une autre fenêtre), dragEnter n'est jamais déclenché. Il est probablement également possible de déplacer la souris assez rapidement pour que le dragEnter ne soit pas déclenché lorsque vous vous déplacez vers l'enfant, mais pas sûr à 100% à ce sujet.
FINDarkside
12

Cela semble être un bogue de Chrome.

La seule solution de contournement à laquelle je pouvais penser était de créer un élément de superposition transparent pour capturer vos événements: http://jsfiddle.net/yYF3S/10/

JS :

$(document).ready(function() {
    var dropzone = $('#overlay');

    dropzone.on('dragenter', function(event) {
        $('#dropzone-highlight').addClass('dnd-hover');
    });

    dropzone.on('dragleave', function(event) {
        $('#dropzone-highlight').removeClass('dnd-hover');
    });

});​

HTML :

<div id="dropzone-highlight">
    <div id="overlay"></div>

    <div id="dropzone" class="zone">
        <div id="drag-n-drop">
            <div class="text1">this is some text</div>
            <div class="text2">this is a container with text and images</div>
        </div>
    </div>
</div>

<h2 draggable="true">Drag me</h2>
Mixeur
la source
merci pour la réponse, Blender. J'ai déjà essayé cela cependant. Le problème avec cette solution est que l'élément de superposition "superpose" les éléments enfants, avec lesquels j'ai besoin d'interagir ... donc avoir une superposition désactive l'interaction dont j'ai besoin avec les enfants ... pensez-y comme un fichier drag-n -drop box ... vous pouvez faire glisser et déposer ou vous pouvez cliquer sur l'élément d'entrée pour sélectionner vos fichiers.
Hristo
voici un exemple de violon que votre solution ne satisfait pas ... jsfiddle.net/UnsungHero97/yYF3S/11
Hristo
Je suis confus. Pourquoi avez-vous également besoin d'interagir avec les éléments enfants?
Blender
Dans mon cas particulier, un élément enfant de la "dropzone" est un bouton de sélection de fichier ... donc si je ne veux pas glisser-déposer de fichiers, je peux les sélectionner en utilisant l'élément d'entrée. Mais avec la superposition ... je ne peux pas cliquer sur l'élément d'entrée. a du sens?
Hristo
@Blender La plupart des cas, nous devons faire glisser un fichier du bureau, pas un élément de la page, donc cela ne fonctionne pas.
Mārtiņš Briedis
5

Le problème est que vos éléments à l'intérieur des dropzones font bien sûr partie de la dropzone et lorsque vous entrez les enfants, vous quittez le parent. Résoudre ce problème n'est pas facile. Vous pouvez également essayer d'ajouter des événements aux enfants en ajoutant à nouveau votre classe au parent.

$("#dropzone,#dropzone *").on('dragenter', function(event) {

    // add a class to #dropzone

    event.stopPropagation(); // might not be necessary
    event.preventDefault();
    return false;
});

Vos événements se déclencheront toujours plusieurs fois mais personne ne le verra.

// Edit: utilisez l'événement dragmove pour écraser définitivement l'événement dragleave:

$("#dropzone,#dropzone *").on('dragenter dragover', function(event) {

    // add a class to #dropzone

    event.stopPropagation(); // might not be necessary
    event.preventDefault();
    return false;
});

Définissez l'événement dragleave uniquement pour la zone de dépôt.

Oliver
la source
2
cela ne résoudra pas le problème. Le vrai problème est dragleave... quand je quitte un enfant, je suis peut-être toujours dans le parent, #dropzonemais cela ne déclenchera pas à dragenternouveau l' événement :(
Hristo
Etes-vous sûr que le dragenter n'est plus déclenché?
Oliver
Ah droite ... il est à nouveau déclenché, cependant, après son déclenchement, l' dragleaveévénement est déclenché pour l'élément enfant juste à gauche, donc à nouveau l' removeClassinstruction est exécutée
Hristo
Je ne peux pas définir dragleaveuniquement pour le #dropzone... si je pouvais, je n'aurais pas ce problème.
Hristo
Non, je voulais dire ne pas ajouter d'événements pour dragleave aux enfants.
Oliver
4

Comme benr l'a mentionné dans cette réponse , vous pouvez empêcher les nœuds enfants de se déclencher sur des événements, mais si vous devez lier certains événements, procédez comme suit :

#dropzone.dragover *{
   pointer-events: none;
}

Et ajoutez celui-ci à votre code JS:

$("#dropzone").on("dragover", function (event) {
   $("#dropzone").addClass("dragover");
});

$("#dropzone").on("dragleave", function (event) {
   $("#dropzone").removeClass("dragover");
});
Vahid Ashrafian
la source
2
Pour moi, cela se traduit par un clignotement constant de la superposition.
Andrey Mikhaylov - lolmaus
3

Si vous utilisez jQuery, vérifiez ceci: https://github.com/dancork/jquery.event.dragout

C'est vraiment génial.

Événement spécial créé pour gérer la véritable fonctionnalité de dragleave.

L'événement HTML5 dragleave fonctionne plus comme mouseout. Ce plugin a été créé pour reproduire la fonctionnalité de style mouseleave tout en faisant glisser.

Exemple d'utilisation:

$ ('# myelement'). on ('dragout', function (event) {// VOTRE CODE});

EDIT: en fait, je ne pense pas que cela dépende de jQuery, vous pouvez probablement simplement utiliser le code même sans.

nkkollaw
la source
Hou la la! Dragout est la seule chose qui a fonctionné pour ma mise en page avec de nombreux éléments enfants déclenchés dragleavemême si je n'ai pas quitté la zone de dépôt parent.
Mark Kasson
Après avoir combattu ce problème pendant la dernière heure, dragoutc'était la seule solution qui fonctionnait pour moi.
Jason Hazel
2

@hristo J'ai une solution beaucoup plus élégante. Vérifiez que si c'est quelque chose que vous pourriez utiliser.

Vos efforts n'ont pas été vains après tout. J'ai réussi à utiliser le vôtre au début, mais j'ai eu différents problèmes dans FF, Chrome. Après avoir passé tant d'heures, cette suggestion a fonctionné exactement comme prévu.

Voici comment se déroule la mise en œuvre. J'ai également profité des repères visuels pour guider correctement les utilisateurs sur la zone de dépôt.

$(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
});
visitesb
la source
vous semblez manquer les définitions pour certaines variables hors de portée, je supposevar dropZoneHideDelay=70, dropZoneVisible=true;
Timo Huovinen
@TimoHuovinen oui, c'est exact. Les deux dropZoneHideDelay, dropZoneVisiblesont nécessaires à travers deux 'on'événements de document dans ma mise en œuvre.
visites du
2

Mes deux cents: Cachez un calque sur votre zone de dépôt, puis affichez-le lorsque vous faites glisser et ciblez le dragleave dessus.

Démo: https://jsfiddle.net/t6q4shat/

HTML

<div class="drop-zone">
  <h2 class="drop-here">Drop here</h2>
  <h2 class="drop-now">Drop now!</h2>
  <p>Or <a href="#">browse a file</a></p>
  <div class="drop-layer"></div>
</div>

CSS

.drop-zone{
  padding:50px;
  border:2px dashed #999;
  text-align:center;
  position:relative;
}
.drop-layer{
  display:none;
  position:absolute;
  top:0;
  left:0;
  bottom:0;
  right:0;
  z-index:5;
}
.drop-now{
  display:none;
}

JS

$('.drop-zone').on('dragenter', function(e){
    $('.drop-here').css('display','none');
    $('.drop-now').css('display','block');
    $(this).find('.drop-layer').css('display','block');
    return false;
});

$('.drop-layer').on('dragleave', function(e){
    $('.drop-here').css('display','block');
    $('.drop-now').css('display','none');
    $(this).css('display','none');
    return false;
});
jeremie.b
la source
Simple et efficace! Je suivais la route stopPropagation () et je n'aimais pas appliquer le gestionnaire d'événements à tous les enfants. Cela semblait compliqué mais cela fonctionne bien et est simple à mettre en œuvre. Bonne idée.
Jon Catmull
1

Donc pour moi, l'approche pointer-events: none;n'a pas très bien fonctionné ... Voici donc ma solution alternative:

    #dropzone {
        position: relative;
    }

    #dropzone(.active)::after {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        content: '';
    }

De cette façon, il n'est pas possible au dragleaveparent (sur un enfant) oudragover un élément enfant. J'espère que cela t'aides :)

* La classe '.active' que j'ajoute quand je dragenterou dragleave. Mais si vous travaillez sans cela, laissez la classe à l'écart.

MMachinegun
la source
1
J'ai essayé cela dans un composant EmberJS D&D et l'ajout de la classe «active» par programme est trop lent environ 40% du temps où la souris entre dans la div parent (vue braise). Pas certain de pourquoi. Mais en laissant cela de côté et cela fonctionne très bien, alors merci pour cette solution simple. Testé sur un Mac sur les dernières versions (à ce jour) de Safari, Chrome, Firefox.
rmcsharry
1

Solution rapide super simple pour cela, non testée de manière approfondie mais fonctionne actuellement dans Chrome.

Excusez le Coffeescript.

  dragEndTimer = no

  document.addEventListener 'dragover', (event) ->
    clearTimeout dragEndTimer
    $('body').addClass 'dragging'
    event.preventDefault()
    return no

  document.addEventListener 'dragenter', (event) ->
    $('section').scrollTop(0)
    clearTimeout dragEndTimer
    $('body').addClass 'dragging'

  document.addEventListener 'dragleave', (event) ->
    dragEndTimer = setTimeout ->
      $('body').removeClass 'dragging'
    , 50

Cela corrige le bogue de scintillement de Chrome, ou du moins la permutation de celui-ci qui me causait des problèmes.

Dom Vinyard
la source
1

Cette réponse peut être trouvée ici:

Dragleave HTML5 déclenché lors du survol d'un élément enfant

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');
        }
    }
});
des zéros et des uns
la source
1

Ma version:

$(".dropzone").bind("dragover", function(e){
    console.log('dragover');
});

$(".dropzone").bind("dragleave", function(e) {
  var stopDrag = false;
  if (!e.relatedTarget) stopDrag = true;
  else {
    var parentDrop = $(e.relatedTarget).parents('.dropzone');
    if (e.relatedTarget != this && !parentDrop.length) stopDrag = true;
  }

  if (stopDrag) {
    console.log('dragleave');
  }
});

Avec cette disposition:

<div class="dropzone">
  <div class="inner-zone">Inner-zone</div>
</div>

Je l' ai fait une décharge de classes d'éléments pour e.target, e.currentTarget, e.relatedTargetpour les deux dragoveretdragleave les événements.

Cela m'a montré qu'en quittant le bloc parent (.dropzone )e.relatedTarget n'est pas un enfant de ce bloc, donc je sais que je suis hors de la zone de dépôt.

mortalis
la source
0

Ici, une des solutions les plus simples ● ︿ ●

Jetez un œil à ce violon <- essayez de faire glisser un fichier dans la boîte

Vous pouvez faire quelque chose comme ceci:

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

Par cela, vous devriez déjà savoir ce qui se passe ici.
Jetez un œil au violon, vous savez.

Diego T. Yamaguchi
la source
0

J'ai eu un problème similaire et je l'ai résolu de cette façon:

Le problème: La fonction drop (ev) est déclenchée lorsque l'utilisateur dépose un élément dans la "drop zone" (élément ul), mais malheureusement aussi lorsque l'élément est déposé dans l'un de ses enfants (éléments li).

Le correctif:

function drop(ev) { 
ev.preventDefault(); 
data=ev.dataTransfer.getData('Text'); 
if(ev.target=="[object HTMLLIElement]")  
{ev.target.parentNode.appendChild(document.getElementById(data));}
else{ev.target.appendChild(document.getElementById(data));} 
} 
Pao
la source
0

J'essayais de l'implémenter moi-même pour une boîte de téléchargement de fichiers où la couleur de la boîte changerait lorsque les utilisateurs faisaient glisser un fichier dans l'espace.

J'ai trouvé une solution qui est un bon mélange de Javascript et de CSS. Supposons que vous ayez une zone de dépôt divavec identifiant #drop. Ajoutez ceci à votre Javascript:

$('#drop').on('dragenter', function() {
    $(this).addClass('dragover');
    $(this).children().addClass('inactive');
});

$('#drop').on('dragleave', function() {
    $(this).removeClass('dragover');
    $(this).children().removeClass('inactive');
});

Ensuite, ajoutez ceci à votre CSS, pour désactiver tous les enfants classera .inactive:

#drop *.inactive {
    pointer-events: none;
}

Ainsi, les éléments enfants seront inactifs tant que l'utilisateur fera glisser l'élément sur la boîte.

David A
la source
0

Je ne me suis senti satisfait d'aucune des solutions de contournement présentées ici, car je ne veux pas perdre le contrôle des éléments enfants.

J'ai donc utilisé une approche logique différente, en la traduisant en un plugin jQuery, appelé jquery-draghandler . Il ne manipule absolument pas le DOM, garantissant des performances élevées. Son utilisation est simple:

$(document).ready(function() {

    $(selector).draghandler({
        onDragEnter: function() {
            // $(this).doSomething();
        },
        onDragLeave: function() {
            // $(this).doSomethingElse();
        }
    });

});

Il traite parfaitement le problème sans compromettre aucune fonctionnalité DOM.

Téléchargement, détails et explications sur son référentiel Git .

Luca Fagioli
la source
Comment obtenir l'événement drag enter dans votre plugin?
Woody
Je n'ai pas réellement testé votre directive, mais je pense que cela ne fonctionne pas si l'élément parent (A) n'entoure pas réellement l'élément que je veux vérifier (B). Par exemple, s'il n'y a pas de remplissage entre le bord inférieur de B et le bord inférieur de A, alors le dragenter ne sera jamais déclenché pour l'élément A, n'est-ce pas?
marcelj
@marcelj L'élément A peut être le corps du document lui-même, vous n'avez donc pas vraiment besoin d'un élément environnant (le corps fait le travail) avec un remplissage minimum. La limitation que j'ai trouvée plus tard avec cette approche est que si vous travaillez avec frames et que votre élément est à la frontière de celui-ci. Dans ce cas, je ne suggère pas cette approche.
Luca Fagioli le
0

J'aime vraiment ce que je vois https://github.com/lolmaus/jquery.dragbetter/mais voulait partager une alternative possible. Ma stratégie générale était d'appliquer un style d'arrière-plan à la zone de dépôt (et non à ses enfants) lors de son entrée par glisser-déposer ou de l'un de ses enfants (via le bullage). Ensuite, je supprime le style lors du glissement de la zone de dépôt. L'idée était lors du passage à un enfant, même si je supprimais le style de la zone de dépôt en le quittant (le dragleave se déclenche), je réappliquerais simplement le style à la zone de dépôt parent en introduisant un enfant par glisser-déposer. Le problème est bien sûr que lors du passage de la zone de dépôt à un enfant de la zone de dépôt, le dragenter est déclenché sur l'enfant avant le dragleave, donc mes styles ont été appliqués dans le désordre. La solution pour moi était d'utiliser une minuterie pour forcer l'événement dragenter à revenir dans la file d'attente des messages, me permettant de le traiter APRÈS le dragleave. J'ai utilisé une fermeture pour accéder à l'événement sur le rappel du minuteur.

$('.dropzone').on('dragenter', function(event) {
  (function (event) {
    setTimeout(function () {
      $(event.target).closest('.dropzone').addClass('highlight');
    }, 0);
  }) (event.originalEvent); 
});

Cela semble fonctionner dans Chrome, c'est-à-dire Firefox et fonctionne quel que soit le nombre d'enfants dans la zone de dépôt. Je suis un peu inquiet quant au délai d'attente garantissant le reséquençage des événements, mais cela semble fonctionner plutôt bien pour mon cas d'utilisation.

méfopole
la source
0

J'essayais de l'implémenter moi-même, et je ne voulais pas non plus de Jquery ou de tout autre plugin.

Je voulais gérer le téléchargement du fichier, de la manière la plus appropriée pour moi:

Structure des fichiers:

--- / uploads {répertoire de téléversements}

--- /js/slyupload.js {fichier javascript.}

--- index.php

--- upload.php

--- styles.css {juste un peu de style ..}

Code HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>my Dzone Upload...</title>
<link rel="stylesheet" href="styles.css" />
</head>

<body>
    <div id="uploads"></div>        
    <div class="dropzone" id="dropzone">Drop files here to upload</div>     
    <script type="text/javascript" src="js/slyupload.js"></script>      
</body>
</html>

Ensuite, voici le fichier Javascript attaché :: 'js / slyupload.js'

<!-- begin snippet:  -->

<!-- language: lang-js -->

    // JavaScript Document

    //ondragover, ondragleave...


    (function(){        

        var dropzone = document.getElementById('dropzone');
        var intv;

        function displayUploads(data){
            //console.log(data, data.length);
            var uploads = document.getElementById('uploads'),anchor,x;

            for(x=0; x < data.length; x++){

                //alert(data[x].file);
                anchor = document.createElement('a');
                anchor.href = data[x].file;
                anchor.innerText = data[x].name;

                uploads.appendChild(anchor);
            }               
        }

        function upload(files){
            //console.log(files);
            var formData = new FormData(), 
                xhr      = new XMLHttpRequest(),    //for ajax calls...
                x;                                  //for the loop..

                for(x=0;x<files.length; x++){
                    formData.append('file[]', files[x]);

                    /*//do this for every file...
                    xhr = new XMLHttpRequest();

                    //open... and send individually..
                    xhr.open('post', 'upload.php');
                    xhr.send(formData);*/
                }

                xhr.onload = function(){
                    var data = JSON.parse(this.responseText);   //whatever comes from our php..
                    //console.log(data);
                    displayUploads(data);

                    //clear the interval when upload completes... 
                    clearInterval(intv);
                }                   

                xhr.onerror = function(){
                    console.log(xhr.status);
                }

                //use this to send all together.. and disable the xhr sending above...

                //open... and send individually..
                intv = setInterval(updateProgress, 50);
                xhr.open('post', 'upload.php');
                xhr.send(formData);

                //update progress... 
                 /* */                   
        }

        function updateProgress(){
            console.log('hello');
         }          

        dropzone.ondrop = function(e){
            e.preventDefault(); //prevent the default behaviour.. of displaying images when dropped...
            this.className = 'dropzone';
            //we can now call the uploading... 
            upload(e.dataTransfer.files); //the event has a data transfer object...
        }

        dropzone.ondragover = function(){
            //console.log('hello');
            this.className = 'dropzone dragover';
            return false;
        }

        dropzone.ondragleave = function(){
            this.className = 'dropzone';
            return false;
        }           
    }(window));

CSS:

body{
	font-family:Arial, Helvetica, sans-serif; 
	font-size:12px;
}

.dropzone{
	width:300px; 
	height:300px;
	border:2px dashed #ccc;
	color:#ccc;
	line-height:300px;
	text-align:center;
}

.dropzone.dragover{
	border-color:#000;
	color:#000;
}

Ande Caleb
la source
0

Désolé, ce n'est pas javascript jquery, mais pour moi, c'est le moyen le plus logique de résoudre ce problème. Le navigateur devrait appeler dropleave (de l'élément précédent) avant dropenter (du nouvel élément, comme quelque chose ne peut pas entrer autre chose avant d'avoir quitté la première chose, je ne comprends pas pourquoi ils l'ont fait! Il vous suffit donc de retarder de dropleave comme ça:

function mydropleave(e)
{
    e.preventDefault();
    e.stopPropagation();

    setTimeout(function(e){ //the things you want to do },1);
}

Et le dropenter se produira après le dropleave, c'est tout!

Entretoize
la source
-1

Utilisez le greedy : trueà l'enfant en fonction de droppable. Ensuite, démarrez uniquement l'événement cliqué sur la première couche.

Adam
la source