Salvador Dali
Salvador Dali

Reputation: 222461

Problems with bubbling event on dragleave

I have a working example with dragenter and dragleave events that highlight an area where the file should be dropped. This example works correctly.

Right now if I just add a single <span> inside of my dragenter region, highlighting does not work correctly anymore (when you hover the image on top of the text - highlighting disappears). As you see dragleave is called multiple times.

All I changed is substituted Drop files here to <span>Drop files here</span>

Also there is knockout code there, but I believe that it has nothing to do with the bug. I understand that the problem is with event bubbling, but

   e.stopPropagation();
   e.preventDefault();
   return false;

does not help. Any idea how to make it work with dom elements indside?

P.S. this is just a simplified example and it looks like I was not able to properly make it (I was thinking that the only way to solve it through JS, and it appears that the way I described it it is possible to solve it with css as well). Sorry for this confusion. Example looks more like this. Not only the Text is inside of the dropable element, but when you drop something, elements appears there. These elements are clickable.

The problem with Malk's solution is that :after element stays on top of these clickable elements and thus making them unclickable.

Upvotes: 2

Views: 1247

Answers (4)

Daniel Baulig
Daniel Baulig

Reputation: 10989

I've found this question and associated answers looking for a solution to this myself. I didn't like any of the solutions. They all involve adding additional elements to the DOM to mask the composite elements of the drag target. They feel like hacks trying to avoid the actual problem: correctly handling the event bubbling behavior. My solution does exactly that and as a result is much cleaner and simpler.

The solution is to simply check if the node that is receiving the drag upon a dragleave event is still contained within the node that you want to be the drop target. E.g.

<div class="dropTarget">
  <span>Composite Element 1</span>
  <span>Composite Element 2</span>
<div>
const dropTarget = document.querySelector('.dropTarget');

dropTarget.addEventListener('dragleave', (event) => {
  if (dropTarget.contains(event.relatedTarget)) {
    // We are still dragging within the dropTarget parent element
    return;
  }
  // We have left the dropTarget parent element
  // Do whatever you want to do in response to 'dragleave'
  // ...
});

Upvotes: 2

PsiKai
PsiKai

Reputation: 1978

I had this problem. And, as with most problems, they can be solved with javascript.

Like OP, I also don't want to have an element cover the entire dropzone and render clickable elements inert.

Essentially, just make sure the mouse position is inside the container's bounding box.

In the dragleave event handler, get the currentTarget's bounding box with getBoundingClientRect.

The drag event will have the clientX and clientY properties which represent the mouse location.

Check that the mouse position falls within the top, right, bottom and left edges of the dropzone. If it is within the bounding box, then return early and don't handle the dragleave event any further.

const handleDragLeave = e => {
  if (isInsideContainer(e)) return
  e.currentTarget.classList.remove("dragged-over")
}

const isInsideContainer = e => {
  const { top, bottom, left, right } = e.currentTarget.getBoundingClientRect()
  return e.clientX < right && e.clientX > left && e.clientY > top && e.clientY < bottom
}

const dropzone = document.querySelector(".dropzone")

dropzone.addEventListener("dragenter", e => {
  e.currentTarget.classList.add("dragged-over")
})

dropzone.addEventListener("dragleave", handleDragLeave)
* {
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  display: grid;
  place-items: center;
}

.dropzone {
  border: 2px dashed black;
  padding: 3rem;
  display: grid;
  place-items: center;
}

.dragged-over {
  border-color: limegreen;
  background-color: lightgrey;
}

span {
  border: 1px solid blue;
}
<div class="dropzone">
  <span>Drag and drop here</span>
</div>

Upvotes: 0

Malk
Malk

Reputation: 11983

It looks like you can attach the handlers to an overlay div that is positioned after and on-top-of the span:

<div class="col-md-12" data-bind="foreach: dropZones">
    <div class="drop_zone">
        <span>Drop files here</span>
        <div class="drop_zone_overlay" data-bind="event:{
                dragover:   function(data, e){ $root.dragover(e);},
                drop:       function(data, e){ $root.drop(e, $data);},
                dragenter:  function(data, e){ $root.dragenter(e, $index());},
                dragleave:  function(data, e){ $root.dragleave(e, $index());}
            }">
          </div>
    </div>
    <ul data-bind="foreach: elements" style="height: 100px">
        <li data-bind="text: $data"></li>
    </ul>
</div>

CSS

.drop_zone {
    border: 2px dashed #bbb;
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    border-radius: 5px;
    padding: 25px;
    text-align: center;
    font: 20pt bold'Vollkorn';
    color: #bbb;

    position:relative;
}

.drop_zone_overlay { 
    position:absolute;
    top:0;
    bottom:0;
    left:0;
    right:0;
}

http://jsfiddle.net/rWWK5/


EDIT

Actually you do not need to add another element at all. You can create a pseudo-element with CSS :after that should work to cover the content.

.drop_zone {
    ...
    position:relative;
}
.drop_zone:after{
    content:'';
    position:absolute;
    top:0;
    left:0;
    right:0;
    bottom:0;
}

http://jsfiddle.net/ewng9/


EDIT 2

You can use this technique to cover the contents only when dragging. You just need to change your .css({}) call to toggleClass() and put the :after on the new class.

http://jsfiddle.net/dKsmw/

This will also lets you create an overlay that tints the background elements:

.drop_zone_hover:after{
    ...
    background-color:#0f0;
    opacity:0.6;
}

Upvotes: 2

Colin Bacon
Colin Bacon

Reputation: 15609

I think Malk's answer is valid. Using an overlay or mask to sit above the drop zone and it's children when you drag over and drop. This prevents the issue you were experiencing with the span

I've created a working Fiddle with your newest example.

Upvotes: 1

Related Questions