Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flickering when dragging over child elements #438

Closed
claudiorivetti opened this issue Jan 2, 2014 · 26 comments
Closed

Flickering when dragging over child elements #438

claudiorivetti opened this issue Jan 2, 2014 · 26 comments

Comments

@claudiorivetti
Copy link

I a building a small filemanager with dropzone.js and it works fine with firefox but with ie, chrome and safari there is a lot of flickering of the border when dragging the mouse oer child elements. Is there a way to avoid triggering the dragleave event when themouse enters child elements?

claudio

@mkasson
Copy link

mkasson commented Jan 2, 2014

Nope that's a thing, but you can code around it. See the discussion here: http://stackoverflow.com/questions/14194324/firefox-firing-dragleave-when-dragging-over-text/20596358#20596358

I found "dragout" to be very useful.

I've found drag enter to have the same issue (it does not fire for children). Its an issue for small parents with larger children with fast mouse movements. One day I may write its counterpart. Dragon?

@claudiorivetti
Copy link
Author

I have solved the issue with Dragster:
https://github.com/bensmithett/dragster

It is small and it works great, may be it can be included in dropzone.

Here what I did:

redefined dragenter and dragleave:
dragleave: function(ev) {return true},
dragenter: function(ev) {return true},

create dragster class:
var ds = new Dragster( div_element );

Add dragster listener
document.addEventListener( "dragster:enter", function (e) {
e.target.classList.add( "dz-drag-hover" );
}, false );

document.addEventListener( "dragster:leave", function (e) {
e.target.classList.remove( "dz-drag-hover" );
}, false );

That's it.

@enyo
Copy link
Collaborator

enyo commented Feb 10, 2014

Will think about how this could be improved

@enyo enyo added the todo label Feb 10, 2014
@malomalo
Copy link

If it helps I fix the issue the following way:

initialize: function () {
    this.dragEnteredEls = [];
},

dragenter: function (e) {
    this.dragEnteredEls.push(e.target);

    if (this.el === e.target) {
        // render hover div
    }
},

dragleave: function (e) {
    this.dragEnteredEls = _.without(this.dragEnteredEls, e.target);
    if (this.dragEnteredEls.length === 0) {
        // cleanup, dragging is finished
    }
}

I use Underscore's without method

@neiltron
Copy link

I was able to work around this by using a transparent overlay.

Basically a div that is 100% height and width of the drop zone that appears when .dz-drag-hover is present on the drop zone. If you add a z-index to it, it will cover up all of the other child elements. I'm really not sure why the overlay element wasn't causing flickering itself, but it works and is CSS only, so 👍

@m1dst
Copy link

m1dst commented Nov 6, 2014

@neiltron Just tried your idea and whilst it works when the hover is not in place the child elements are not clickable. EG: links. Text is selectable though. I need to try something else now.

@jhubert
Copy link

jhubert commented Aug 7, 2015

Here is some sample code that I'm using to fix this. I've packaged it up so that it should be able to just drop into your projects. ✨

function setupDragon(uploader) {
    /* A little closure for handling proper 
       drag and drop hover behavior */
    var dragon = (function (elm) {
      var dragCounter = 0;

      return {
        enter: function (event) {
          event.preventDefault();
          dragCounter++;
          elm.classList.add('dz-drag-hover')
        },
        leave: function (event) {
          dragCounter--;
          if (dragCounter === 0) {
            elm.classList.remove('dz-drag-hover')
          }
        }
      }
    })(uploader.element);

    uploader.on('dragenter', dragon.enter);
    uploader.on('dragleave', dragon.leave);
}

// You can set it up via the custom intialization
new Dropzone('div#dropzone', {
  url: "/upload",
  /* overwrite the default behavior */
  dragenter: function () {},
  dragleave: function () {},
  /* setup the new behavior */
  init: function () { setupDragon(this) }
});

// Or using the options like this
Dropzone.options.dropzone = {
  /* overwrite the default behavior */
  dragenter: function () {},
  dragleave: function () {},
  /* setup the new behavior. 'this' is the uploader */
  init: function () { setupDragon(this) }
}

Thanks to @mkasson for the name idea. 👍

@drwpow
Copy link

drwpow commented Jan 21, 2016

@jhubert Works like a dream! Thanks so much for this.

jevanlingen added a commit to jevanlingen/ember-cli-dropzonejs that referenced this issue Jul 27, 2016
If dragged over the child element, the dz-drag-hover class is removed. See dropzone/dropzone#438 for more info.

It's unlikely this problem will be fixed in the core of dropzonejs soon, so applied the fix made by @jhubert. Code can be deleted as soon dropzonjs team fixes this.
@kamelkev
Copy link

kamelkev commented Feb 17, 2017

@jhubert Reference counting will only get you so far with this problem. Depending on browser (firefox) and sophistication of the dropzone (nested elements) it will rapidly break down.

I've personally used something very similar to what @malomalo describes, though he's not particularly clear with his description.

Basically the idea is that you have an array, and you add e.target for every single dragEnter event you receive. You similarly remove every single e.target you receive within dragLeave.

When the array is of length 0 you remove the hover effect within dragLeave. You may unconditionally add the hover effect within dragEnter.

You'll note that removing a specific element from an array can be a bit of a pain here, though it's rather easy with underscore or jQuery. The other poster roughly covers the underscore approach, here is how it'd look with jQuery:

init: function() {
  this.dragEnteredEls = $();
},
dragenter: function(e) {
  this.dragEnteredEls.add(e.target);

  $(this.element).addClass('dz-drag-hover');
},
dragleave: function(e) {
  this.dragEnteredEls = $(this.dragEnteredEls).not(e.target);

  if (this.dragEnteredEls.length === 0) {
    $(this.element).removeClass('dz-drag-hover');
  }
},

Not exactly hard, and covers all the edge cases that I'm aware of. I've had something in production using something similar to the above for about 6 months without issue now.

@Ydalb
Copy link

Ydalb commented Feb 25, 2017

Thanks @kamelkev !

@kamelkev
Copy link

kamelkev commented Mar 27, 2017

@Ydalb It's worth noting my original post here had a minor error. You want "$()" instead of "new Array" for dragEnteredEls, otherwise there are some cross browser issues. You then want to use the jquery specific array modifiers (because dragEnteredEls is now a jquery object), after which everything works as expected.

Apologies. I've fixed my example, you might want to fix up your merge and double check it.

@Haraldson
Copy link

Haraldson commented Mar 31, 2017

I’ve been stuck with drag events the entire day, and just realized that my use case is slightly different: I want to show dropzones when a file dragenters the viewport, and hide them again when it dragleaves. However, as dropzone.js stops event propagation in its internal dragenter event handler, document will never get the event, with the result that moving the file onto the dropzone is counted as a dragleave instead.

I’ve modified the source code so that dragenter events bubbles from the dropzone, but I don’t know what side effects this change might have. Any insight here? Or proposals for better workarounds?

@Haraldson
Copy link

Looking at this more closely, it seems more and more like there’s some confusion between stopping event propagation and preventing default browser behaviors, since both are called from a function named noPropagation, weirdly. They don’t do the same thing, at all.

I’ve commented out most instances of calls to the noPropagation function, and replaced with e.preventDefault() where needed, in order to allow for other parts of the code to listen in on what’s going on – as I would expect to be able to do out of the box.

It would be nice if someone with deeper insight into and knowledge of the library, and the decisions leading up to it being the way it is, would review this part of the code and see if this could be updated to allow for event propagation.

@jameswilson
Copy link

For my case @kamelkev solution worked perfectly AND he was exactly right about @jhubert solution causing issues in Firefox (I got bitten).

@Haraldson it seems like your proposal would probably make sense as a separate issue and Pull Request.

@sorenwiz
Copy link

sorenwiz commented Oct 19, 2017

The solutions didn't work for me. Used a counter instead
Edit: Ended up using @kamelkev fix instead

@kamelkev
Copy link

@sorenfu Such a solution cannot and will not work with Firefox. You likely won’t notice problems elsewhere, but you will note obvious tabulation issues if you test in Firefox.

The underlying reason relates to how Firefox decides to treat the enter event. Entering a certain elements may allow multiple enter events to fire, however there won’t be an analogous set of exit events to match. The net result is that your counter can always increase, but won’t necessarily decrease as expected.

The solution I have posted is tried and true. My team spent an inordinate amount of time testing the technique, which is now used by multiple other organizations as well. You should retry it.

@sorenwiz
Copy link

@kamelkev you are right. The reason why I still had was the flickering, was because I had a :before element on .dz-drag-hover with a text. I moved the text into a child tag and that did it. js ❤️

@Nessworthy
Copy link

Nessworthy commented Oct 25, 2017

Just figured out a sneaksy way of doing this.
If you give yourself something like this:

.dz-drag-hover {
    position: relative;
}
.dz-drag-hover:after {
    display: block;
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;
    background-color: transparent;
    z-index: 1000;
}
.dz-drag-hover .your-visual-dropzone-upload-message {
    /* ... */
}

And you style the element that you want shown to the user so the z-index isn't above that 1000, it doesn't flicker.
The idea is that the :after is overlaid on top as a transparent layer so the drag event is kept on that, but your upload visual is still shown because of the presence of the .dz-drag-hover class on a parent container.

@serbiant
Copy link

@kamelkev your fix didn't work for us initially. However thanks for the tip, good job - I did a simple change and everything works now in any browser:

dragenter: function(e) {
  this.dragEnteredEls = this.dragEnteredEls.add(e.target);

  $(this.element).addClass('dz-drag-hover');
},

@kamelkev
Copy link

That's interesting. Are we saying that .add does not add the passed item to the collection, but instead returns a whole new collection? That would be incredibly interesting if so...

Maybe someone else can comment regarding whether they had similar issues or not?

@kthalmann
Copy link

@kamelkev I just had the same issue as @serbiant did and used his fix.

@micaww
Copy link

micaww commented Jan 26, 2018

Same here @kamelkev. Seems that .add doesn't change the original collection:

From https://api.jquery.com/add/

The following will not save the added elements, because the .add() method creates a new set and leaves the original set in pdiv unchanged:

var pdiv = $( "p" );
pdiv.add( "div" ); // WRONG, pdiv will not change

@kamelkev
Copy link

@micaww Good to know.

@jeremyhalin
Copy link

Hi, I'm having this issue, is there a fix now ?

@ahmed-am
Copy link

ahmed-am commented Aug 1, 2019

I had the same problem, here's a simple fix that worked for me.
(written in Typescript, but the Javascript/Jquery equivalent should work fine)

1- on the 'dragover' event, call a functin that adds a class to the dropzone container (e.g., .no-pointer-events)

in your css file

.no-pointer-events * { pointer-events: none; }
,

The function:

public onFileDrag() {
        this.isFileDragging = true;
        if (this.draggingTimeout) {
            clearTimeout(this.draggingTimeout);
        }
        this.draggingTimeout = setTimeout(() => {
            this.isFileDragging = false;
        }, 500);
    }

Basically, this is to keep the class on the dropzone to disable all children pointer events as long as there is a file hovering, when there is none, the children return to normal after half a second

@enyo enyo closed this as completed Feb 5, 2021
@rlconst
Copy link

rlconst commented Oct 17, 2023

Here's my approach for case @Haraldson for scenario when I want to see dropzone only when I hovering files.

I really think it should be righ inside library

    let dragging = false
    function debounce(func, delay = 250) {
        let timerId;
        return (...args) => {
            clearTimeout(timerId);
            timerId = setTimeout(() => {
                func.apply(this, args);
            }, delay);
        };
    }
    document.ondragenter = function (e) {
        dragging = true
        if (e.dataTransfer.types?.includes('Files')) {
            document.getElementById("dropzone").classList.remove("d-none")
        }
    }
    const debouncedHide = debounce(() => {
        if (!dragging) {
            document.getElementById("dropzone").classList.add("d-none")
        }
    }, 100)
    document.ondragleave = function (e) {
        dragging = false
        debouncedHide()
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests