39

When I drag an element over another div on which I have a mouseover event, the event doesn't trigger. However, it works if I hover over it without dragging.

Is there a way to detect hover events on an element if I drag another one over it?

Purag
  • 16,941
  • 4
  • 54
  • 75
V.Rashkov
  • 1,527
  • 4
  • 18
  • 31
  • Are you using jQuery UI? – Purag Dec 23 '11 at 09:12
  • No, i'm using custom created dragg – V.Rashkov Dec 23 '11 at 09:13
  • It would help to see that code. Either put it in the question or paste it into a [jsfiddle](http://jsfiddle.net). – Purag Dec 23 '11 at 09:14
  • 4
    If you mouseover an element which is covered by another element, the mouseover event doesn't fire (unless the covering element is a child of the element, in which case it bubbles). You're going to have to do whatever you're trying by X and Y position instead, I'm afraid. – Nathan MacInnes Dec 23 '11 at 09:18
  • 1
    X and Y coordinates is very tedious work to do, and very error prone. Just place the dragged element besides the cursor so it doesn't block mouse interaction with the elements behind it. – Munter Dec 23 '11 at 09:35

7 Answers7

17

In all presented answers, I don't see the most simple and obvious one (maybe I'm missing something in OP question). But, if someone stumble upon this later, and needs fast and simple solution in pure JS..

You do it by changing element className ondragover, and changing back to original class ondragleave

my_element.ondragover = function(ev) {  
 ev.preventDefault();  
 this.className = 'myElem_dragover';  
}  
my_element.ondragleave = function(ev) {  
 ev.preventDefault();  
 this.className = 'myElem_orig';  
}

CSS

.myElem_orig {     //this is your initial class for element
  top: 30px;
  left: 20px;
  .....
  background-color: blue;  
}  

.myElem_orig:hover {   //this is hover state, just changing bg color
  background-color: red;
}

.myElem_dragover { //new class, needs all attributes from original class
  top: 30px;
  left: 20px;
  ........ 
  background-color: red; //behaves the same like hover does
}

edit:
forgot to mention, you need to bring back original class ondrop too, otherwise div will stay in dragover class

Wolf War
  • 1,503
  • 1
  • 15
  • 28
  • 1
    I think this question pre-dates widespread use of the built-in HTML draggable attribute, which is definitely the easiest way to go unless you're doing custom behaviors. – Grae Kindel Mar 10 '15 at 12:31
  • That's what I was looking for, not any custom behavior. Thanks. – Wellyngton Feb 25 '16 at 17:51
  • I agree with greg. This answer is excellent now-a-days and will get popular very soon I feel. – Lucas Jan 27 '17 at 15:31
  • One downside to the dragover/dragout is that it can't tell, as far as I know, which side of an element you are hovering over. Say it's a list of items that you are drag'n'drop sorting, if you hover over the top half of an item, you'd want to dragged content to be placed ABOVE that item. But you can only see that you are hovering on it, now the location within. – Kelderic Jun 07 '17 at 12:58
  • @AndyMercer in `dragOver` event you can test on which half the mouse is, up or down, and according to that make decision. With the `object.getBoundingClientRect()` you get for instance, bottom border and subtract mouse Y coordinate from it. You get value that is bigger or lover than object height/2 – Wolf War Jun 07 '17 at 14:25
  • Kinda funny, I just posted a question asking about that, and your comment here may have just answered it. I'll investigate the `dragover` event some more, thanks. – Kelderic Jun 07 '17 at 14:30
  • @AndyMercer `getBoundingClientRect()` is not part of the drag&drop API if you were thinking that. It's just that in `dragOver` event you can test many things, on of them is bounding rectangle, and calculate position with pure math. – Wolf War Jun 07 '17 at 14:50
  • Yep, that is the logic I used. I gave you credit in the answer to the question I posted. https://stackoverflow.com/questions/44415228/list-sorting-with-html5-dragndrop-drop-above-or-below-depending-on-mouse/44416461#44416461, thanks mate! – Kelderic Jun 07 '17 at 15:09
13

Here is an example using the X-Y coordinate solution.

Working example on jsfiddle

The example can be improved, but is a good starting point.

Simply keeps track of the mouse location and checks if it appears to be inside any bounding boxes of the droppable objects. Hence, if the mouseup event fires on any one of them, dragged object is dropped.

You can also use the coordinates of the object you are dragging for detecting if its on a droppable box, but it requires a little more code for finding the bounding box coords and using the mouse is enough for me.

The code uses jQuery but no jQueryUI. I tested in Chrome, Firefox and Opera, but not IE :)

I'm also adding the code to here if jsfiddle is not accessible.

HTML

<p>Drag orange boxes to grey ones</p>
<div class="droppable"></div>
<div class="droppable"></div>
<div class="droppable"></div>
<div class="droppable"></div>

<div class="draggable"></div>
<div class="draggable"></div>
<div class="draggable"></div>

CSS

.droppable {
    width:50px;
    height:50px;
    float: left;
    background-color: #DDD;
    margin: 5px;
}

.draggable {
    width:40px;
    height:40px;
    float: right;
    background-color: #FC0;
    margin: 5px;
    cursor: pointer;
}

.dropped {
    background-color: #FC0;
}

.somethingover {
    background-color: #FCD;
}

JS

var dragged, mousex, mousey, coordinates = [];

var continueDragging = function(e) {
    // Change the location of the draggable object
    dragged.css({
        "left": e.pageX - (dragged.width() / 2),
        "top": e.pageY - (dragged.height() / 2)
    });

    // Check if we hit any boxes
    for (var i in coordinates) {
        if (mousex >= coordinates[i].left && mousex <= coordinates[i].right) {
            if (mousey >= coordinates[i].top && mousey <= coordinates[i].bottom) {
                // Yes, the mouse is on a droppable area
                // Lets change the background color
                coordinates[i].dom.addClass("somethingover");
            }
        } else {
            // Nope, we did not hit any objects yet
            coordinates[i].dom.removeClass("somethingover");
        }
    }

    // Keep the last positions of the mouse coord.s
    mousex = e.pageX;
    mousey = e.pageY;
}

var endDragging = function(e) {
    // Remove document event listeners
    $(document).unbind("mousemove", continueDragging);
    $(document).unbind("mouseup", endDragging);

    // Check if we hit any boxes
    for (var i in coordinates) {
        if (mousex >= coordinates[i].left && mousex <= coordinates[i].right) {
            if (mousey >= coordinates[i].top && mousey <= coordinates[i].bottom) {
                // Yes, the mouse is on a droppable area
                droptarget = coordinates[i].dom;
                droptarget.removeClass("somethingover").addClass("dropped");
                dragged.hide("fast", function() {
                    $(this).remove();
                });
            }
        }
    }

    // Reset variables
    mousex = 0;
    mousey = 0;
    dragged = null;
    coordinates = [];
}

var startDragging = function(e) {
    // Find coordinates of the droppable bounding boxes
    $(".droppable").each(function() {
        var lefttop = $(this).offset();
        // and save them in a container for later access
        coordinates.push({
            dom: $(this),
            left: lefttop.left,
            top: lefttop.top,
            right: lefttop.left + $(this).width(),
            bottom: lefttop.top + $(this).height()
        });
    });

    // When the mouse down event is received
    if (e.type == "mousedown") {
        dragged = $(this);
        // Change the position of the draggable
        dragged.css({
            "left": e.pageX - (dragged.width() / 2),
            "top": e.pageY - (dragged.height() / 2),
            "position": "absolute"
        });
        // Bind the events for dragging and stopping
        $(document).bind("mousemove", continueDragging);
        $(document).bind("mouseup", endDragging);
    }
}

// Start the dragging
$(".draggable").bind("mousedown", startDragging);
emrahgunduz
  • 1,404
  • 1
  • 13
  • 26
4

There are two basic ways you can do this:

  1. track mousemove and react to x/y coordinates
  2. have a transparent target that has a higher z-index than the drag container

First option doesn't really use the mouseover event at all, but will give you the same net result.

Be aware that some browsers (ie) won't trigger mouseover on transparent elements, so you have to fake it by setting a background image that is transparent or setting a random image as background and positioning it outside the element like this:

element {
 background: url(/path/to/img) no-repeat -10000px 0;
}
Martin Jespersen
  • 25,743
  • 8
  • 56
  • 68
  • That *might* interfere with the element being dragged, depending on how he has it set up. – Purag Dec 23 '11 at 10:00
1

jQuery-ui has a droppable plugin for this.

The plugin, when used with a draggable element will trigger dropover events, which can be bound to any action you require.

See Mottie's answer to this question (demo included)

Community
  • 1
  • 1
willscripted
  • 1,438
  • 1
  • 15
  • 25
1

Modifing a bit the code posted by emrahgunduz, specifically the for loop, you can also manage nested droppable area.

var dragged, mousex, mousey, coordinates = [];

var continueDragging = function(e) {
    // Change the location of the draggable object
    dragged.css({
        "left": e.pageX - (dragged.width() / 2),
        "top": e.pageY - (dragged.height() / 2)
    });

    // Check if we hit any boxes
    for (var i = coordinates.length - 1; i >= 0; i--) {
        if (mousex >= coordinates[i].left && mousex <= coordinates[i].right) {
            if (mousey >= coordinates[i].top && mousey <= coordinates[i].bottom) {
                // Yes, the mouse is on a droppable area
                // Lets change the background color
                $('.droppable').removeClass("somethingover");
                coordinates[i].dom.addClass("somethingover");
                break;
            }
        } else {
            // Nope, we did not hit any objects yet
            coordinates[i].dom.removeClass("somethingover");
        }
    }

    // Keep the last positions of the mouse coord.s
    mousex = e.pageX;
    mousey = e.pageY;
};

var endDragging = function(e) {
    // Remove document event listeners
    $(document).unbind("mousemove", continueDragging);
    $(document).unbind("mouseup", endDragging);

    // Check if we hit any boxes
    for (var i = coordinates.length - 1; i >= 0; i--) {
        if (mousex >= coordinates[i].left && mousex <= coordinates[i].right) {
            if (mousey >= coordinates[i].top && mousey <= coordinates[i].bottom) {
                // Yes, the mouse is on a droppable area
                droptarget = coordinates[i].dom;
                droptarget.removeClass("somethingover").addClass("dropped");
                dragged.hide("fast", function() {
                    $(this).remove();
                });
            }
        }
    }

    // Reset variables
    mousex = 0;
    mousey = 0;
    dragged = null;
    coordinates = [];
};

var startDragging = function(e) {
    // Find coordinates of the droppable bounding boxes
    $(".droppable").each(function() {
        var lefttop = $(this).offset();
        // and save them in a container for later access
        coordinates.push({
        dom: $(this),
        left: lefttop.left,
        top: lefttop.top,
        right: lefttop.left + $(this).width(),
        bottom: lefttop.top + $(this).height()
    });
};

// When the mouse down event is received
if (e.type == "mousedown") {
    dragged = $(this);
    // Change the position of the draggable
    dragged.css({
        "left": e.pageX - (dragged.width() / 2),
        "top": e.pageY - (dragged.height() / 2),
        "position": "absolute"
    });
    // Bind the events for dragging and stopping
    $(document).bind("mousemove", continueDragging);
    $(document).bind("mouseup", endDragging);
}

// Start the dragging
$(".draggable").bind("mousedown", startDragging);
gigaDIE
  • 388
  • 4
  • 10
1

Another possible solution to the problem of when the dragged element is blocking the hover or mouseenter event on the element under it:

pointer-events: none;

If this is applied to the dragged element then hover should still work on the element beneath.

Paulsarms
  • 11
  • 1
0

Found a small bug in the jsfiddle example. When you leave the drop area vertically, the drop area still has the 'somethinghover' class.

http://jsfiddle.net/MAazv

Replace this

if (mousex >= coordinates[i].left && mousex <= coordinates[i].right) {
  if (mousey >= coordinates[i].top && mousey <= coordinates[i].bottom) {
    // Yes, the mouse is on a droppable area
    // Lets change the background color
    coordinates[i].dom.addClass("somethingover");
  }
} else {
  // Nope, we did not hit any objects yet
  coordinates[i].dom.removeClass("somethingover");
}

http://jsfiddle.net/MAazv/122

with this:

if (mousex >= coordinates[i].left && mousex <= coordinates[i].right && mousey >= coordinates[i].top && mousey <= coordinates[i].bottom) {
  // Yes, the mouse is on a droppable area
  // Lets change the background color
  coordinates[i].dom.addClass("somethingover");
} else {
  // Nope, we did not hit any objects yet
  coordinates[i].dom.removeClass("somethingover");
}