Drip-drop is a simple generalized drag-and-drop module that abstracts away most of the drag-and-drop APIs weirdness and surprises. This library simply handles drag-and-drop events in all the major cases:
- within a browser window,
- between browser windows
- into a browser window from an external program (including uploading files from your desktop),
- and to an external program from a browser window.
It can be this easy:
var dd = require("drip-drop")
var draghandle = dd.drag(myDomNode), {
image: true, // default drag image
})
draghandle.on('start', function(setData, e) {
setData('myCustomData', JSON.stringify({a:1, b:"NOT THE BEES"})) // camel case types are allowed!*
})
dd.drop(myDropzone).on('drop', function(data, e) {
myDropzone.innerHTML = data.myCustomData
})
Check out the demo!
Man does the HTML5 drag and drop API suck big giant donkey balls! And all the existing drag-and-drop modules I could find are either tied to a framework like Angular or React, or are trying to give you complex libraries for moving elements around on the page. I wanted a generalized library I could use as the basis for any drag-and-drop situation without being bloated by code that is only needed in a subset of the situations.
npm install drip-drop
Accessing drip-drop:
// node.js
var dd = require('drip-drop')
// amd
require.config({paths: {'drip-drop': '../generatedBuilds/drip-drop.umd.js'}})
require(['drip-drop'], function(dd) { /* your code */ })
// global variable
<script src="drip-drop.umd.js"></script>
dripDrop; // drip-drop.umd.js can define dripDrop globally if you really
// want to shun module-based design
Using drip-drop:
dd.drag(domNode, options)
- Sets up drag-related events on the domNode
. Returns an EmitterB instance that emits the drag events described below.
domNode
- The domNode to be set as a drag source (you can then drag from that element).options
image
- Can take on one of the following possible values:false
- (Default) No image.true
- The default generated drag image.aString
- The path to an image to show next to the cursor while dragging.imageObject
- If this is an Image object, the image it represents will be used
- Emitted events:
start(setData, e)
- This function will be called when dragging starts. Use setData to set the data for each type.setData(type,stringData)
- Sets data for a particular type.- NOTE: In an attempt mitigate type lower-casing weirdness, capitals will be converted to dash-lowercase and lowercase without dashes. Drip-drop's
drop
function will convert back to camel case. Eg. using the type "camelCase" will set the value on both the type "camelcase" and "camel-case". - CAVEAT: Internet Explorer only allows two possible values for 'type':
"text"
and"url"
. IE isn't making any friends here. Complain about it: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/329509/
- NOTE: In an attempt mitigate type lower-casing weirdness, capitals will be converted to dash-lowercase and lowercase without dashes. Drip-drop's
e
- The original Drag Event object.- Note that
e.dataTransfer.effectAllowed
can be set to an allowedEffect - defaults to "all".
- Note that
move(e)
- This function will be called when the drag event moves position. Note that the pointer position can be grabbed frome.pageX
ande.pageY
.end(e)
- This function will be called when the drag event has been either completed or canceled.
dd.drop(domNode, options)
- Sets up drop-related events on the domNode
. Returns an EmitterB instance that emits the drag events described below.
domNode
- The domNode to be set as a drop-zone.options
allow
- A list of types to allow the event handlers be called for. If this is passed and the current drag operation doesn't have an allowed type, the handlers will not be called. If this isn't passed, all types are allowed.
- Emitted Events:
enter(types, e)
- A function called when a drag action enters the node- types - The data types available on drop. If any types have the sequence dash-then-lowercase-letter, the type will exist in its original form and in a camel cased from. Eg.
["text", "camel-case"]
will be transformed into["text", "camel-case", "camelCase"]
. Also note that the data associated with the types is only available in the 'drop' event for security reasons (imagine if someone was dragging a password from one program to another, but passed over a browser window first). e
- The original Drag Event object.
- types - The data types available on drop. If any types have the sequence dash-then-lowercase-letter, the type will exist in its original form and in a camel cased from. Eg.
in(types, e)
- A function called when the dragging pointer crosses in over a child-boundary of a descendant nodemove(types, e)
- This function will be called when the drag event moves position over the drop-zone. The return value of this will be set as the dropEffect.- Note that
e.dataTransfer.dropEffect
can be set to a dropEffect. - Note that the pointer position can be grabbed from
e.pageX
ande.pageY
.
- Note that
out(types, e)
- A function called when the dragging pointer crosses out over a child-boundary of a descendant nodeleave(types,e)
- A function called with the dragging pointer moves out of the node or is canceled.drop(data, e)
- This function will be called when the dragging pointer releases above the node.data
- An object where each key is a data type. If a type contains dashes, the type will be available as-is and with dash-lowercase converted to camel case (matching thetypes
described above). The value with either be:- For the 'Files' type, the value is a list of files, each with a set of properties described here: https://developer.mozilla.org/en-US/docs/Web/API/File . In addition, the files have the methods:
getText(errback)
- Returns the text of the file in a call to the the errback.getBuffer(errback)
- Returns a Buffer of the file contents in a call to the the errback.
- For any other type, the value is a string of data in a format depending on the type
- For the 'Files' type, the value is a list of files, each with a set of properties described here: https://developer.mozilla.org/en-US/docs/Web/API/File . In addition, the files have the methods:
dd.dontPreventDefault()
- Unsets some document-level handlers that prevent the defaults for 'dragenter' and 'dragover'. If you call this, you will need to call event.preventDefault()
in the appropriate dd.drop
'event' and 'move' handlers.
dd.ghostItem(domNode[, zIndex])
- Returns a semi-transparent clone of the passed dom node ready to be moved with dd.moveAbsoluteNode
.
- zIndex - (Default: 1000) - The zIndex to give to the returned clone.
dd.moveAbsoluteNode(domNode, x, y)
- Moves an absolutely positioned element to the position by x and y.
dd.drop(myDropzone).on('drop', function(data, e) {
if(data.Files) {
data.Files.forEach(function(file) {
console.log("Name: "+file.name)
console.log("Size: "+file.size)
var fileContents = file.getText()
// do something with the contents
})
}
})
These two functions are basic helper functions for doing the common drag visualization of creating a semi-transparent clone of what you're dragging and moving it along with your mouse.
var ghostItem, draghandle = dd.drag(myDomNode)
draghandle.on('start', function(setData, e) {
setData('myCustomData', "Through counter-intelligence it should be possible to pinpoint potential troublemakers, and neutralize them.")
ghostItem = dd.ghostItem(myDomNode.parent)
document.body.appendChild(ghostItem)
})
draghandle.on('move', function(event) {
dd.moveAbsoluteNode(ghostItem, event.pageX, event.pageY)
})
draghandle.on('end', function() {
document.body.removeChild(ghostItem)
})
The bizzarities that drip-drop abstracts away from the garbagy native drag-and-drop API
- Handles canceling the default at the appropriate times
- Prevents dragging a file on the wrong spot from loading that file, which would kill your application (this can be turned off if for some reason you want that behavior)
- Replaces the 'dragover' event (which fires even when your pointer isn't moving) with the 'move' event (which only fires when your pointer moves)
- Replaces the 'dragleave' and 'dragenter' event (which fires even when your pointer doesn't exit/enter the dropzone if it crosses child-node boundaries) with 'leave' and 'enter' (which doesn't do that stupid BS)
- Provides the 'in' and 'out' event handlers which fire when your pointer crosses the first child-node boundary of a descendant node that is also a dropzone
- Allows you to use camelcase in setData 'types' (see description of the 'start' event for caveats)
- Only 6 event-types to care about (rather than the 8 from the spec)
- Provides an easy and obvious way to make changes related to the source element on-pointer-move (drag's 'move' event)
If you want to detect when drag events are occuring and when they end, set up drag
events on the document body:
document.body.addEventListener('dragstart', function() {
// dragging has started somewhere
})
document.body.addEventListener('dragend', function() {
// dragging has ended somewhere (works even if dragging ended off screen)
})
Drip drop should work on all desktop browsers. However, Android and iOS browsers don't support the html5 drag and drop API for touch events. So if you want to translate touch events into html5 drag events, you can do that with a polyfill. See here: https://www.codeproject.com/Articles/1091766/Add-support-for-standard-HTML-Drag-and-Drop-operat
- Chrome and Opera seem to have a bug where the 'dragend' will sometime fail to fire if an iframe is move within or attached to the DOM. https://bugs.chromium.org/p/chromium/issues/detail?id=737691
** To work around this, wait until dragging has stopped to manipulate iframes (see the
Tricks
section)
- Capture-phase drag events
Anything helps:
- Creating issues (aka tickets/bugs/etc). Please feel free to use issues to report bugs, request features, and discuss changes
- Updating the documentation: ie this readme file. Be bold! Help create amazing documentation!
- Submitting pull requests.
How to submit pull requests:
- Please create an issue and get my input before spending too much time creating a feature. Work with me to ensure your feature or addition is optimal and fits with the purpose of the project.
- Fork the repository
- clone your forked repo onto your machine and run
npm install
at its root - If you're gonna work on multiple separate things, its best to create a separate branch for each of them
- edit!
- When you're done, run the unit tests and ensure they all pass
- Commit and push your changes
- Submit a pull request: https://help.github.com/articles/creating-a-pull-request
- 2.0.3 - Improving last bug fix to not be blocked by stopPropagation calls
- 2.0.2 - Fixing bug where enter and leave would stop working if you didn't have a drop handler and dropped onto that dropzone
- 2.0.1 - BREAKING CHANGE Changing to a more standard EventEmitter API. Old API is retained for backwards compatibility except that the return value is now an emitter rather than a function that removes the drag handlers.
- 1.0.3 - Fixing bug where stopPropagation in a drag move event prevented drop
- 1.0.1
- Changing
in
andout
to fire for every child-node boundary crossing (because I don't think drop zones can be programatically detected) - Adding demo
- Changing
- 1.0.0 - Adding
in
andout
drop event handlers. - 0.0.7 - Fixing bug where stopPropagation wasn't working consistently for dragover events
- 0.0.6 - Removing left over pointer parameter
- 0.0.5 - Fixing dragleave and dragenter not being called after the first drop
- 0.0.4 - Fixing dragleave failing to fire sometimes and fixing dragenter firing too often
- 0.0.3
- Removing errant
e.preventDefault()
that was breaking this - Removing extraneous pointer position arguments
- Removing errant
- 0.0.2 - Implementing allowedEffect and documenting dropEffect.
- 0.0.1 - first commit!
Released under the MIT license: http://opensource.org/licenses/MIT