192

Overview

I have the following HTML structure and I've attached the dragenter and dragleave events to the <div id="dropzone"> element.

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

Problem

When I drag a file over the <div id="dropzone">, the dragenter event is fired as expected. However, when I move my mouse over a child element, such as <div id="drag-n-drop">, the dragenter event is fired for the <div id="drag-n-drop"> element and then the dragleave event is fired for the <div id="dropzone"> element.

If I hover over the <div id="dropzone"> element again, the dragenter event is again fired, which is cool, but then the dragleave event is fired for the child element just left, so the removeClass instruction is executed, which is not cool.

This behavior is problematic for 2 reasons:

  1. I'm only attaching dragenter & dragleave to the <div id="dropzone"> so I don't understand why the children elements have these events attached as well.

  2. I'm still dragging over the <div id="dropzone"> element while hovering over its children so I don't want dragleave to fire!

jsFiddle

Here's a jsFiddle to tinker with: http://jsfiddle.net/yYF3S/2/

Question

So... how can I make it such that when I'm dragging a file over the <div id="dropzone"> element, dragleave doesn't fire even if I'm dragging over any children elements... it should only fire when I leave the <div id="dropzone"> element... hovering/dragging around anywhere within the boundaries of the element should not trigger the dragleave event.

I need this to be cross-browser compatible, at least in the browsers that support HTML5 drag-n-drop, so this answer is not adequate.

It seems like Google and Dropbox have figured this out, but their source code is minified/complex so I haven't been able to figure this out from their implementation.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Hristo
  • 45,559
  • 65
  • 163
  • 230

22 Answers22

186

If you don't need to bind events to the child elements, you can always use the pointer-events property.

.child-elements {
  pointer-events: none;
}
Ben Rudolph
  • 2,509
  • 2
  • 19
  • 26
  • 4
    Best solution! I didn't even think that I could solve the problem using css in such an elegant way. Thank you very much! – serg66 Jan 15 '13 at 13:47
  • 25
    The only downside to this approach is that it nukes all pointer events on child elements (eg they can no longer have separate `:hover` styles or click event handlers). In case you want to preserve those events, here's another workaround I've been using: http://bensmithett.github.io/dragster/ – Ben Oct 28 '13 at 05:57
  • 2
    Combine with css child selector to reach all children of your dropzone: `.dropzone * {pointer-events: none;}` – Philipp F Jun 10 '15 at 09:00
  • 9
    You're already using jQuery, so just `$('.element').addClass('dragging-over')` to the element you're dragging over, and `$('.element').removeClass('dragging-over')` when you drag out. Then in your CSS you can have `.element.dragging-over * { pointer-events: none; }`. That removes pointer events from all child elements only when you're dragging over. – Gavin Oct 15 '15 at 01:32
  • This solution worked amazingly well when using jQuery and jQuery UI in a large grid system with hundreds of elements. My issue was that the text inside the container would be selectable and users would attempt to drag and drop a span instead of the drag/drop li item. This simple CSS removed the ability and works great. – Ryan Rentfro Feb 14 '16 at 19:29
  • Genius. Pure genius. – Rijk Nov 15 '16 at 17:54
  • Wow! Perfect answer! – Daniel May 15 '17 at 16:01
  • This is *ALMOST* perfect... With one exception: If the children are `input`s of type `checkbox` or `radio`, then `pointer-events: none` will have no effect. This is because `pointer-events` are only those that result in a change of *VALUE* on an element. In the case of `checkbox` and `radio`, these change their *STATE*, not their value. – Jack_Hu Mar 04 '22 at 11:32
65

I finally found a solution I'm happy with. I actually found several ways to do what I want but none were as successful as the current solution... in one solution, I experienced frequent flickering as a result of adding/removing a border to the #dropzone element... in another, the border was never removed if you hover away from the browser.

Anyway, my best hacky solution is this:

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

This works pretty well but issues came up in Firefox because Firefox was double-invoking dragenter so my counter was off. But nevertheless, its not a very elegant solution.

Then I stumbled upon this question: How to detect the dragleave event in Firefox when dragging outside the window

So I took the answer and applied it to my 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;
    }
});

This is clean and elegant and seems to be working in every browser I've tested so far (haven't tested IE yet).

Community
  • 1
  • 1
Hristo
  • 45,559
  • 65
  • 163
  • 230
  • This is the best solution so far.The only problem is this code doesn't work when you add a file, remove it and drag another file over it. Only after raising HoverEnd and raising HoverStart again this code works again. – MysticEarth May 07 '15 at 20:48
  • @MysticEarth can you put together a jsFiddle to demo when it doesn't work? – Hristo May 08 '15 at 03:54
  • 1
    Thank you for your answer. This was really helpful, as the HTML5 drag and drop interface and event listeners can be really painfull. This is the best way to handle the crazy events, especially when you want to handle multiple instances of drop zone elements. – Nico O Oct 19 '15 at 14:13
  • Why do we need to call stopPropagation() preventDefault() and return false. Return false should be enough. Should It ? – Dejo Dec 24 '15 at 17:13
  • It is redundant, but calling `stopPropagation()` and `preventDefault()` is preferred. I'd drop the `return false`. – Peeja Aug 08 '16 at 16:04
46

This is a little ugly but it works dammit!...

On your 'dragenter' handler store the event.target (in a variable inside your closure, or whatever), then in your 'dragleave' handler only fire your code if event.target === the one you stored.

If your 'dragenter' is firing when you don't want it to (i.e. when it's entering after leaving child elements), then the last time it fires before the mouse leaves the parent, it's on the parent, so the parent will always be the final 'dragenter' before the intended 'dragleave'.

(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
  • 1,360
  • 1
  • 16
  • 20
  • I'll give this a try later. If anything, it looks cleaner than my solution but I can't be sure how cross-browser compatible it is just by looking at it. Where have you tested this? – Hristo Sep 04 '12 at 20:33
  • 1
    only Chrome, and may only work on an assumption that there is a physical gap between the child elements and the container. – hacklikecrack Sep 04 '12 at 20:43
  • what do you mean physical gap? – Hristo Sep 04 '12 at 20:59
  • as in there are pixels between the child element boundaries and the parent boundary. Haven't tested. – hacklikecrack Sep 05 '12 at 09:14
  • Seems to work, thanks! Something to note is that I had to wrap any text inside the top-level `div` (`#dropzone`) in a span. Don't ask me why, but if I didn't then `dragleave` would fire whenever I dragged over a character. – Harry Cutts Jul 16 '13 at 15:33
  • 5
    My favourite answer on here. Doesn't feel ugly at all :) – Matt Way Dec 12 '13 at 11:56
  • this does not work if I do drop. I have to use this approach: https://github.com/bingjie2680/jquery-draghover – bingjie2680 Jun 19 '14 at 12:26
  • 1
    Works for me in IE, Chrome, FF – Vicentiu Bacioiu Dec 27 '14 at 10:36
  • Given some additional complications with three.js, this is the only solution that worked in my circumstance. Thanks! – 16807 May 17 '15 at 04:02
  • 1
    This does not work if the drag start on the children. For example, dragging from something from another window in a way that drag starts inside the child. – FINDarkside Feb 02 '19 at 15:26
  • Thanks a loooooot @hacklikecrack. It saved my day – Ana DEV May 19 '22 at 08:12
41

At first, I agreed with folks discarding the pointer-events: none approach. But then I asked myself:

Do you really need pointer-events to work on the child elements while dragging is in progress?

In my case, I have lots of stuff going on in the children, e.g. hover to show buttons for additional actions, inline-editing, etc... However, none of that is necessary or in fact even desired during a drag.

In my case, I use something like this to turn pointer events off selectively for all child nodes of the parent container:

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

Use your favorite approach to add/remove the class dragging-in-progress in the dragEnter/dragLeave event handlers, as I did or do the same in dragStart, et. al.

bargar
  • 584
  • 5
  • 5
  • 1
    This solution is both the simplest and is (as far as I can tell) full-proof. Simply add the class in the `dragstart` event and remove it in `dragend`. – cmann May 15 '15 at 21:46
  • This is pretty nice solution but not perfect. If the drag starts on the child element (dragging from another window), dragEnter is never fired. It's probably also possible to move mouse fast enough that the dragEnter is not fired when you move to the child, but not 100% sure about that. – FINDarkside Feb 02 '19 at 15:21
  • I confirm @FINDarkside comment on being able to drag fast enough to skip a few events, and mess up the process. – sw1337 Sep 21 '20 at 18:50
12

This seems to be a Chrome bug.

The only workaround that I could think of was to create a transparent overlay element to capture your events: 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>
​
Blender
  • 289,723
  • 53
  • 439
  • 496
  • thanks for the answer, Blender. I've already tried this however. The problem with this solution is that the overlay element "overlays" the children elements, which I need to interact with... so having an overlay disables the interaction I need with the children... think of it as a file drag-n-drop box... you can drag and drop or you can click the input element to select your files. – Hristo Jun 03 '12 at 03:18
  • here's an example fiddle that your solution does not satisfy... http://jsfiddle.net/UnsungHero97/yYF3S/11/ – Hristo Jun 03 '12 at 03:19
  • I'm confused. Why do you need to interact with the child elements as well? – Blender Jun 03 '12 at 03:20
  • In my particular case, a child element of the "dropzone" is a file select button... so if I don't want to drag and drop files, I can select them using the input element. But with the overlay... I can't click the input element. make sense? – Hristo Jun 03 '12 at 03:22
  • @Blender Most cases we need to drag a file from desktop, not an element from page, so this kinda not works. – Mārtiņš Briedis Jul 24 '15 at 13:03
5

The problem is, that your elements inside the dropzones are of course part of the dropzone and when you enter the children, you leave the parent. Solving this is not easy. You might try adding events to the children too adding your class again to the parent.

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

    // add a class to #dropzone

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

Your events will still fire multiple times but nobody will see.

//Edit: Use the dragmove event to permanently overwrite the dragleave event:

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

    // add a class to #dropzone

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

Define the dragleave event only for the dropzone.

Oliver
  • 2,864
  • 1
  • 16
  • 27
  • 2
    that won't solve the problem though. The real issue is `dragleave`... when I leave a child, I might still be within the parent `#dropzone` but that won't fire the `dragenter` event again :( – Hristo Jun 03 '12 at 03:01
  • Are you sure, that the dragenter is not fired again? – Oliver Jun 03 '12 at 03:04
  • Ah right... it is fired again, however, after it is fired, the `dragleave` event is fired for the child element just left, so again the `removeClass` instruction is executed – Hristo Jun 03 '12 at 03:06
  • I can't define `dragleave` only for the `#dropzone`... if I could, I wouldn't have this problem. – Hristo Jun 03 '12 at 03:21
  • No, I meant don't add events for dragleave to the children. – Oliver Jun 03 '12 at 03:22
  • Just replace your dragenter code and let the dragleave as it is. The children should not fire a dragleave event. – Oliver Jun 03 '12 at 03:25
  • So don't define a 'dragleave' event at all? – Hristo Jun 03 '12 at 03:26
  • Look http://jsfiddle.net/PsWm8/ - oh and why don't you use the jQuery functions for events and selectors? – Oliver Jun 03 '12 at 03:29
  • As you can see it does not really matter here, because dragover is stronger. ;-) – Oliver Jun 03 '12 at 03:37
  • Oh interesting... Ok I'll tinker with it some more later and get back to you. Thanks! – Hristo Jun 03 '12 at 03:38
4

As benr mentioned in this answer, you can prevent child nodes to fire on events, but if you need to bind some events, do this:

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

And add this one to your JS code:

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

$("#dropzone").on("dragleave", function (event) {
   $("#dropzone").removeClass("dragover");
});
Community
  • 1
  • 1
4

My two cents: Hide a layer over your dropzone then show it when you dragenter, and target the dragleave on it.

Demo : 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
  • 41
  • 2
  • Simple and effective! I was going down the stopPropagation() route and I didn't like applying event handler to all children. It seemed messy but this works well and is simple to implement. Good idea. – Jon Catmull Jan 11 '17 at 16:39
3

If you're using jQuery, check this out: https://github.com/dancork/jquery.event.dragout

It's truly awesome.

Special event created to handle true dragleave functionality.

HTML5 dragleave event works more like mouseout. This plugin was created to replicate the mouseleave style functionality whilst dragging.

Usage Example:

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

EDIT: actually, I don't think it's dependent on jQuery, you can probably just use the code even without it.

nkkollaw
  • 1,947
  • 1
  • 19
  • 29
  • Wow! Dragout is the ONE thing that worked for my layout with plenty of children elements firing `dragleave` even though I haven't left the parent dropzone. – Mark Kasson Dec 15 '13 at 06:46
  • After fighting with this issue for the last hour, `dragout` was the only solution that worked for me. – Jason Hazel Mar 29 '19 at 17:18
3

Sorry it's javascript not jquery, but for me it's the most logical way to solve that. The browser should call dropleave (of the previous element) before dropenter (ofr the new element, as something can't enter something else before having leaved the forst thing, I don't understand why they did that ! So you just need to delay de dropleave like that:

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

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

And the dropenter will happen after the dropleave, that's all !

Entretoize
  • 2,124
  • 3
  • 23
  • 44
2

@hristo I have a much more elegant solution. Check that if that is something you could use.

Your effort was not wasted after all. I managed to use yours at first, but had different problems in FF, Chrome. After spending so many hours I got that suggestion working exactly as intended.

Here's how the implementation is. I also took advantage of visual cues to correctly guide users about the drop zone.

$(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
});
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
visitsb
  • 541
  • 4
  • 8
  • you seem to be missing the definitions for some out of scope variables, I assume `var dropZoneHideDelay=70, dropZoneVisible=true;` – Timo Huovinen Mar 13 '14 at 15:20
  • @TimoHuovinen yes, correct. Both `dropZoneHideDelay, dropZoneVisible` are needed across two `'on'` document events in my implementation. – visitsb Mar 15 '14 at 11:07
2

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

With this layout:

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

I've made a dump of element classes for e.target, e.currentTarget, e.relatedTarget for both dragover and dragleave events.

It showed me that on leaving the parent block (.dropzone) e.relatedTarget is not a child of this block so I know I'm out of the dropzone.

mortalis
  • 2,060
  • 24
  • 34
1

So for me the approach pointer-events: none; didn't work too well... So here is my alternative solution:

    #dropzone {
        position: relative;
    }

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

This way it is not possible to dragleave the parent (on a child) or dragover a child element. hope this helps :)

*The '.active'-class I add when I dragenter or dragleave. But if you're working without that just leave the class away.

MMachinegun
  • 3,044
  • 2
  • 27
  • 44
  • 1
    I tried this in an EmberJS D&D component and adding the 'active' class programmatically is too slow about 40% of the time the mouse enters the parent div (ember-view). Not sure why. But leaving that off and it works great, so thank you for this simple solution. Tested on a Mac on latest versions (as of today) of Safari, Chrome, Firefox. – rmcsharry Sep 11 '16 at 09:23
1

I did not feel satisfied with any of the workarounds presented here, because I do not want to lose control over the children elements.

So I've used a different logic approach, translating it into a jQuery plugin, called jquery-draghandler. It does absolutely not manipulate the DOM, guaranteeing high performances. Its usage is simple:

$(document).ready(function() {

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

});

It deals flawlessly with the problem without compromising any DOM functionality.

Download, details and explanations on its Git repository.

Luca Fagioli
  • 12,722
  • 5
  • 59
  • 57
  • How do I get the drag enter event in your plugin ? – Woody Apr 26 '15 at 18:52
  • I haven't actually tested your directive, but I think it does not work if the parent element (A) doesn't actually surround the element I want to check for (B). For example, if there is no padding between the bottom edge of B and the bottom edge of A, then dragenter will never be fired for element A, right? – marcelj Jan 31 '16 at 16:40
  • @marcelj Element **A** can be the body document itself, so you do not really need a surrounding element (body does the job) with a minimum padding. The limitation I've found later with this approach is that if you work with `frame`s and your element is at the border of it. In that case, I do not suggest this approach. – Luca Fagioli Feb 14 '16 at 16:53
1

Super simple quick fix for this, not tested extensively but works in Chrome right now.

Excuse the 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

This fixes the Chrome flicker bug, or at least the permutation of it that was causing me issues.

Dom Vinyard
  • 2,038
  • 1
  • 23
  • 36
1

I'm actually liking what I see at https://github.com/lolmaus/jquery.dragbetter/ but wanted to share a possible alternative. My general strategy was to apply a background style to the dropzone (not its children) when dragentering it or any child of it (via bubbling). Then I remove the style when dragleaving the drop zone. Idea was when moving to a child, even if I remove the style from the dropzone when leaving it (the dragleave fires), I would simply reapply the style to the parent dropzone on dragentering any child. Problem is of course that when moving from the dropzone to a child of the dropzone, the dragenter gets fired on the child before the dragleave, so my styles got applied out of order. Solution for me was to use a timer to force the dragenter event back to the message queue, allowing me to process it AFTER the dragleave. I used a closure to access the event on the timer callback.

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

This seems to work in chrome, ie, firefox and works regardless of the number of children in the dropzone. I'm slightly uneasy about the timeout guaranteeing the resequencing of events, but it seems to work pretty well for my use case.

mefopolis
  • 31
  • 3
1

This answer can be found here:

HTML5 dragleave fired when hovering a child element

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');
        }
    }
});
Community
  • 1
  • 1
zeros-and-ones
  • 4,227
  • 6
  • 35
  • 54
0

Here, one of the simplest solution ●︿●

Take a look at this fiddle <-- try dragging some file inside the box

You can do something like this:

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

By that you should already know whats happening here.
Just take a look at the fiddle, you know.

0

I've had a similar problem and I fixed it this way:

The problem: The function drop(ev) is fired when the user drops an element in the "drop zone" (ul element), but, unfortunately, also when the element is dropped in one of its children ( li elements).

The fix:

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
  • 206
  • 3
  • 10
0

I was trying to implement this myself for a file upload box where the box color would change when users dragged a file into the space.

I found a solution that is a nice mix of Javascript and CSS. Say you have a droppable zone with div with id #drop. Add this to your Javascript:

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

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

Then, add this to your CSS, to inactivate all children will class .inactive:

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

Thus, the children elements will be inactive for as long as the user is dragging the element over the box.

David A
  • 717
  • 1
  • 8
  • 18
-1

I was trying to implement this myself, and I did not want Jquery or any plugin either.

I wanted to handle the file upload, following way deemed best for me:

Files Structure:

--- /uploads {uploads directory}

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

--- index.php

--- upload.php

--- styles.css {just a little styling..}

HTML Code:

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

Then This is the attached Javascript File:: '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;
}
Vikrant
  • 4,920
  • 17
  • 48
  • 72
Ande Caleb
  • 1,163
  • 1
  • 14
  • 35
-2

Use the greedy : true to the child as a function of droppable. Then start only event clicked on the first layer.

Athena
  • 3,200
  • 3
  • 27
  • 35
Adam
  • 1