Here's what i'm trying to achieve:
There are multiple dropzones on the page. Users should be able to drag files from their OS and drop them into the dropzones.
Dropzones get highlighted during dragging. There are two visually different types of highlighting: "Target" (for example, the element gets outlined with a dashed border) and "Hover" (for example, the element gets a bright background).
The Target highlighting is applied/removed on/from all dropzones at once:
- When a user drags a file over the page, all dropzones should be highlighted with the Target highlighting.
- When a user drags the file outside the page, or cancels the drag/drop operation, or performs the drop, then the Target highlighting should be removed from all the dropzones.
The Hover highlighting should be applied to only one dropzone:
- When a user drags a file over a dropzone, that dropzone should be highlighted with the Hover highlighting.
- When a user drags the file outside that dropzone, or cancels the drag/drop operation, or performs the drop, then the Target highlighting should be removed from the dropzone.
When a user drops a file on a dropzone, the file's name should appear inside the dropzone.
When a user drops a file on the page outside dropzones, all dropzones highlighting should be removed and nothing else should happen. Specifically, the dropped file should not be opened by the browser.
The solution should be as much graceful as possible: dirty hacks like using timeouts, counting
dragenter
/dragleave
events and reapplying highlighting on everydragover
are not welcome.The solution should work in latest versions of major browsers.
Here's what i've managed to achieve so far: attempt 1, attempt 2.
Dropping a file outside the dropzone resulted in the browser opening the file.
Solution:
$(document).on('dragover drop', function (e) {
e.preventDefault();
});
Dropping a file into a dropzone generates a drop
event with target equal to a child of the dropzone rather than the dropzone itself.
Solution:
$dropzones.on( 'drop', function (event) {
/* ... */
// Find the dropzone responsible for the event
$targetDropzone = $(event.target).closest($dropzones);
/* ... */
});
Hovering the file over dropzone's children generates multiple dragleave
events, making the Hover highlighting disappear immediately (the Hover highlighting should be removed from a dropzone when the mouse cursor leaves the dropzone, so it's bound to the dragleave
event).
Solution: use the dragout
event instead of dragleave
. dragout
is a custom event provided by the jquery.event.dragout plugin. This event won't fire for element's children.
Unable to detect the moment when the dragged file leaves the document
or window
, so that a "remove Target highlighting" command could be executed.
The custom dragout
plugin is designed to work only for children of <body>
. It works neither for document
nor window
.
The official dragstart
and dragend
events won't work at all for dragging files. This is an expected behavior. :(
The dragenter
and dragleave
events bound to document
are triggered not only when the mouse pointer enters/leaves the document but also when the pointer enters/leaves document's children. Worse of all, the event.target
of first $(document).on('dragenter')
occurrence may appear as one element (it can be document
or its child), and the last occurrence of $(document).on('dragleave')
can appear as a different element, so you can't resolve the issue by comparing event.target
s.
Due to these issues, i failed to gracefully track the moment when the mouse leaves the document.
I have attempted to use the draghover plugin that is aimed to resolve this issue. I managed to make it work (tested only in Chrome), but it would stop working correctly after the first successful drop. See the failed attempt here.
Though it's impossible to tell visually, the dragenter
event gets fired many times while the file hovers over the document. Thus, highlighting is applied multiple times instead of one.
The mouse pointer during drag should display the browser's standard "can't drop here" icon when hovering outside dropzones and the "can drop here" icon when hovering over dropzones.
Hi comrade! Thank you for a detailed reply. Unfortunately, there's a number of issues with your solution.
- Unable to detect the moment when the dragged file leaves the document or window, so that a "remove Target highlighting" command could be executed.
$(document).on('dragleave', … must do the trick, see the fiddle below.
Nope, this is very bad.
Let's say you listen for dragenter
and dragleave
events on <body>
. Whenever a dragging mouse pointer hovers across an edge of any element, two events will trigger:
dragenter
on <body>
with event.target
set to the hovered element;dragleave
on <body>
with event.target
set to parent of the hovered element.I supposed that the Target highlighting would be applied with a .dropzone.target-higlighing
selector. You did a witty trick by applying the Target highlighting with a .target-highlighting .dropzone
selector.
Have a look at your code:
$('body')
.on('dragenter', function (event) {
$(event.target).addClass('target-highlighting');
event.preventDefault();
})
.on('dragleave drop', function (event) {
$(event.target).removeClass('target-highlighting-class');
event.preventDefault();
})
If the dropzone resides in a number of nested containers, dragging a file across the containers will result in the target highlighting class migrate form the outermost container to the innermost. Due to the fact that you used a .target-highlighting .dropzone
selector in CSS, it looks like that the target highlighting stands...
...until you drag the file over an element which is not one of dropzone's parents. It could be a sidebar or the dropzone itself. When this happens, the .target-highlighting .dropzone
selector stops being applied and the target highlighting disappears.
This is not acceptable. The target highlighting should only appear when a file is dragged onto the page and removed when the file is dragged out of the page or when the dragging has been finished (by drop or cancellation).
- Though it's impossible to tell visually, the dragenter event gets fired many times while the file hovers over the document. Thus, highlighting is applied multiple times instead of one.
The event is fired every time when the mouse enters some element. So, as you drag over the page it enters many elements and fires many times. To avoid this you need to "disable" everything underneath each droparea, there are two ways to do this.
First, is to use css pointer events, this is the most elegant, but least browser friendly solution. It works with most recent ones, and I personally love it.
Second, is to create a transparent overlay on top of the droparea – the mouse will only hit that and not the elements underneath, which will prevent multiple drag enter events.
These solutions are acceptable for triggering the Hover highlighting which is applied while the mouse pointer is inside the dropzone. (BTW, i've found a more graceful solution for this: a dragout
event plugin, see #3 in the Solved Problems section above.)
But they are totally inappropriate for the Target highlighting which should be applied while the mouse pointer is both inside and outside of the dropzone. You'd have to disable mouse events (with either pointer-events: none;
or an overlay) for the whole page, and dropzones would no longer accept drops.
- The mouse pointer during drag should display the browser's standard "can't drop here" icon when hovering outside dropzones and the "can drop here" icon when hovering over dropzones.
I'm not 100% certain on this, but on MAC I can't seem to change the icon while dragging, as it uses the special default one. I assume this can't be done, but would love to learn otherwise.
I've noticed that what i ask already works in Chrome! See the link below.
Firefox would not change the mouse pointer though. :(
There are also some pretty good looking libraries, like http://www.dropzonejs.com/, which I don't have experience with, yet they are a good source of "inspiration".
I've seen this plugin. It does not resolve the issues described above. The Target highlighting is not applied at all, and the Hover highlighting flickers as you drag a file over the dropzone.
Also, i can't use it because i have my own dropzone implementation. For example, my dropzone allows users to sort files added to the dropzone. I only require a solution to handle the drag events.
My personal advice would be to use per-droparea plugin approach, not per-page approach as in your examples. Those components tend to grow pretty big once you add the uploading logic, validation, etc.
You're absolutely right. In my project, i use the wonderful jQuery UI Widget Factory. It's a method of defining jQuery plugins which behave separately from each other.
Here i created a better boilerplate to test further solutions on: http://jsbin.com/rupaloba/4/edit?html,css,js,output
I hope it's not too complicated.
HTML Drag and Drop interfaces enable web applications to drag and drop files on a web page. This document describes how an application can accept one or more files that are dragged from the underlying platform's file manager and dropped on a web page.
Use the Esc Key and Left Click Locate the file or folder you wish to move by left-clicking on it on your desktop. Hit the “Escape” key on your keyboard once. Release the left-click on your mouse. Drag and drop functionality should now work as normal.
Drag and drop is now supported on the Windows 11 taskbar. Quickly drag and drop files between app windows, by hovering over apps in the taskbar to bring their windows to the foreground.
Privet Andrey! I've recently faced most of those, will try to share the knowledge.
1. Unable to detect the moment when the dragged file leaves the document or window, so that a "remove Target highlighting" command could be executed.
$(document).on('dragleave', …
must do the trick, see the fiddle below.
2. Though it's impossible to tell visually, the dragenter event gets fired many times while the file hovers over the document. Thus, highlighting is applied multiple times instead of one.
The event is fired every time when the mouse enters some element. So, as you drag over the page it enters many elements and fires many times. To avoid this you need to "disable" everything underneath each droparea, there are two ways to do this.
First, is to use css pointer events, this is the most elegant, but least browser friendly solution. It works with most recent ones, and I personally love it.
Second, is to create a transparent overlay on top of the droparea – the mouse will only hit that and not the elements underneath, which will prevent multiple drag enter events.
3. The mouse pointer during drag should display the browser's standard "can't drop here" icon when hovering outside dropzones and the "can drop here" icon when hovering over dropzones.
I'm not 100% certain on this, but on MAC I can't seem to change the icon while dragging, as it uses the special default one. I assume this can't be done, but would love to learn otherwise. You can use a different design, like background color change or maybe add a cursor-like div that will follow the mouse. The example shows the trick with the background.
Fiddle with the examples: http://jsfiddle.net/ianbytchek/Q6uEp/8/
That concerns the questions. My personal advice would be to use per-droparea plugin approach, not per-page approach as in your examples. Those components tend to grow pretty big once you add the uploading logic, validation, etc. In a nutshell:
$(document).on('dragenter dragover drop', function…
prevents from opening files in the browser and navigating away.There are also some pretty good looking libraries, like http://www.dropzonejs.com/, which I don't have experience with, yet they are a good source of "inspiration".
I've also used the following in my code to cover up for pointer-events
in older browsers (but never tested it really) – it checks if the mouse is outside the element's boundaries.
// jQuery event configuration.
jQuery.event.props.push('dataTransfer', 'pageX', 'pageY');
element.on('dragleave', function ( event) {
var elementPosition = element.offset();
var elementWidth = element.width();
var elementHeight = element.height();
if (event.pageX < elementPosition.left || event.pageX > elementPosition.left + elementWidth || event.pageY < elementPosition.top || event.pageY > elementPosition.top + elementHeight) {
element.removeClass(States.HIGHLIGHTED);
}
// …
// …
// …
Update 1 (2014-03-16 19:00)
@Andrey'lolmaus'Mikhaylov, you're right on those points – it's a mess once you start nesting things. It played further with it and it turned out to be a real bitch, so I got intrigued. I had little luck solving it with dragenter
and dragleave
events, but I'm sure the solution exists. I did comeup with something less appealing though: http://jsfiddle.net/ianbytchek/Q6uEp/14/
It's a fairly neat solution and I think it's going to be cleaner than what you can get with other apporaches. At the same time it feels a little hacky with all the coordinate checks. I'm tired looking at it, if it gets polished to a better / neater version it would be great to know.
When i learned how the dragenter
and dragleave
events work, i figured out that there's no other option but to count event.target
elements.
I understood that the draghover.js plugin is very close to what i need.
So i rewrote it in an $.event.special
code style for more convenient usage and also modified it so that it doesn't fail after a successful drop.
Here you are: https://github.com/lolmaus/jquery.dragbetter
Demo: http://jsbin.com/rupaloba/15/edit?html,js,output
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With