Polyfill for HTML 5 drag'n'drop
The HTML 5 drag'n'drop API allows you to implement drag'n'drop on most desktop browsers and some mobile browsers.
Unfortunately, you'll notice most mobile browsers don't support it, so no iPad (or Nexus) action for you!
Chrome>=96 on Android>=7
and Safari on iOS/iPadOS>=15
are reported to support drag and drop natively!
This means native support for drag and drop is growing but some browsers still need polyfilling.
It is advised to keep an eye on caniuse and test for your userbase.
In the case of iOS native support and the polyfill seem to be able to coexist without issues.
See https://github.com/timruffles/mobile-drag-drop/issues/167 for state of drag and drop in iOS/iPad>=15
.
Chrome>=96 on Android>=7
behaviour is under investigation.
Luckily, browsers give us enough tools to make it happen ourselves if needed. If you drop
this package in your page your existing HTML 5 drag'n'drop code should just work (*almost).
Demos
Demo
Check out the demo to see it in action and monitor the console to see the events firing.
Install
npm
npm install mobile-drag-drop --save
jspm
jspm install npm:mobile-drag-drop
Include
global
<link rel="stylesheet" href="libs/mobile-drag-drop/release/default.css">
<script src="libs/mobile-drag-drop/release/index.min.js"></script>
<script src="libs/mobile-drag-drop/release/scroll-behaviour.min.js"></script>
<script>
MobileDragDrop.polyfill({
dragImageTranslateOverride: MobileDragDrop.scrollBehaviourDragImageTranslateOverride
});
</script>
SystemJS/JSPM
System.import("mobile-drag-drop");
System.import("mobile-drag-drop/default.css!");
ES2015/TypeScript/webpack
import {polyfill} from "mobile-drag-drop";
import {scrollBehaviourDragImageTranslateOverride} from "mobile-drag-drop/scroll-behaviour";
polyfill({
dragImageTranslateOverride: scrollBehaviourDragImageTranslateOverride
});
Make sure to implement a dragenter
-listener! (read here why)
// dragenter listener
(event)=> {
event.preventDefault();
}
If you're targeting iOS Safari 10.x and higher
window.addEventListener( 'touchmove', function() {}, {passive: false});
See #77 and #124 for details.
webpack/scss
@import "~mobile-drag-drop/default.css";
API & Options
export interface Point {
x: number;
y: number;
}
export type DragImageTranslateOverrideFn = (
event: TouchEvent,
hoverCoordinates: Point,
hoveredElement: HTMLElement,
translateDragImageFn: (offsetX: number, offsetY: number) => void;
) => void;
export interface Config {
forceApply?:boolean;
dragImageOffset?:Point;
dragImageCenterOnTouch?:boolean;
iterationInterval?:number;
dragStartConditionOverride?:( event:TouchEvent ) => boolean;
dragImageTranslateOverride?:DragImageTranslateOverrideFn;
defaultActionOverride?:( event:TouchEvent ) => void;
holdToDrag?:number;
tryFindDraggableTarget?:( event:TouchEvent ) => HTMLElement | undefined;
dragImageSetup?:( element:HTMLElement ) => HTMLElement;
elementFromPoint?:( x:number, y:number ) => Element;
}
export function polyfill(override?: Config):boolean;
Custom events
When setting the option holdToDrag
the draggable element will emit custom events:
dnd-poly-dragstart-pending
as soon as the touchstart
event is detected and a drag operation is about to be started after the delay specified with holdToDrag
dnd-poly-dragstart-cancel
when the drag operation will not be started due to touchmove
, touchend
, touchcancel
or scroll
within the holdToDrag
delay.
Those events can be used to visualize the holdToDrag
so the user is informed that a drag operation is about to start.
DragImage Customization
If you want to set a custom drag image use setDragImage().
Override the classes that are applied by the polyfill for customizing the drag image appearance
and snapback behaviour. Mind the !important
.
.dnd-poly-drag-image {
opacity: .5 !important;
}
.dnd-poly-drag-image.dnd-poly-snapback {
-webkit-transition: -webkit-transform 250ms ease-out !important;
-moz-transition: -moz-transform 250ms ease-out !important;
-o-transition: -o-transform 250ms ease-out !important;
transition: transform 250ms ease-out !important;
}
.dnd-poly-drag-icon {
}
CSS classes are applied to the dragImage
-element according to the
current drop effect: none
, copy
, move
, link
.
There is icons.css
which defines default styles and icons.
Feel free to use this as a starting point.
<link rel="stylesheet" href="[...]/mobile-drag-drop/icons.css">
Custom drag image setup function
One can also set a custom dragImageSetup()
function in the polyfill config. This allows to completely
customize the routine used to create a copy of the dragged element.
Checkout the default implementation as a starting point.
Known issues and limitations
-
iFrames
are currently not supported. Please see #5 for the current state.
-
:before/:after
css pseudo styles can't be copied to the drag image. By default classes are removed on the drag image recursively to avoid side-effects. You can pass a custom dragImageSetup function in the config.
Contributions welcome!
Browser compatibility
Browser | Support | Known issues |
---|
Chrome | Native | No known issues. More info |
Firefox | Native | No known issues. More info |
Safari | Native | No known issues. |
Opera | Native | Same as Chrome. |
Brave | Native | Same as Chrome. |
Internet Explorer 11 | Native | No known issues. |
Edge | Native | No known issues. More info |
Mobile Safari (<iOS 10) | Polyfill | No known issues. |
Mobile Safari (>=iOS 10) | Polyfill | #77 |
Mobile Safari (>=iOS 15) | Native & Polyfill | #167 |
Chrome on iOS | Polyfill | See Mobile Safari since it's the same engine inside. |
Chrome on Android | Polyfill | No known issues. Needs investigation regarding native capabilities! |
Chrome on touch device | Polyfill | No known issues. More info |
Firefox on touch device | Native | No known issues. |
Firefox on Android | Polyfill | No known issues. |
Amazon Silk | Unknown | Unknown |
Ubuntu Phone | Polyfill | No known issues. |
IEMobile | Native | Unknown |
Chrome:
Chrome supports touch devices/events. When run on a desktop touch device like MS Surface it turns on touch events
which also disables native drag-and-drop support. Touch events can also be set by a user in chrome://flags
to auto
, on
, off
.
There is also a flag for enabling drag-and-drop through touch interaction but only for Windows and the option is off by default.
The polyfill still works if this setting is active. We cannot detect if this flag is set so we just stick to applying the polyfill
when Chrome is detected with touch events enabled.
Firefox:
Touch events can be activated by a user in about:config
to 0
(off), 1
(on), 2
(auto).
As of today (FF39.0) touch behavior is off.
When touch events are active drag-and-drop interaction will still work, so no need to polyfill.
Cross-browser differences in HTML5 drag'n'drop API
The drag'n'drop API is not implemented consistently in all browsers.
This table is an effort to list all things required to make drag'n'drop work in all browsers and with the polyfill.
Browser | dragstart | drag | dragend | dragenter | dragover | dragleave | dragexit |
---|
Firefox | event.dataTransfer.setData(type, data) | | | effectAllowed,dropEffect | effectAllowed,dropEffect | | |
IE11 | | | | event.preventDefault() when registered on body | | | |
Polyfill | | | | event.preventDefault() or dropzone required | | | |
empty cells mean there is nothing special to take into account
Polyfill requires dragenter listener
On desktop browsers if no dragenter
-handler is registered the drag-operation is silently allowed. Browsers don't implement dropzone
-attribute
according to caniuse which is why they allow it by default, which violates the spec.
If a handler is set up it has to call event.preventDefault()
to allow dropping.
This is pretty bad for the polyfill since JS doesn't allow to check how many listeners were invoked when the event is dispatched,
which forces the polyfill to rely on a listener being present calling event.preventDefault()
to make it work.
Further notices:
- FF: If
effectAllowed
or dropEffect
is set in dragstart
then dragenter/dragover
also need to set it. - When using a MS Surface tablet a drag-operation is initiated by touch and hold on a draggable.
- IE11, Chrome (and all other browsers using the same engine), Firefox scroll automatically when dragging close to a viewport edge.
Baseline recommendations for cross-browser/-platform support:
- Always set drag data on
dragstart
by calling event.dataTransfer.setData(type, data)
. - Always handle
dragenter
-event on possible dropzones if the drop is allowed by calling event.preventDefault()
. - Handle
dragover
-event on dropzone when the drop is allowed by calling event.preventDefault()
, otherwise the drag-operation is aborted.
Contribute
Contributions are welcome.
For more details on development setup see CONTRIBUTING.md
Thanks
To the amazing contributors who've provided massive extensions and fixes to the original.
@rem - who created the original demo used to demo this shim's drop-in nature.
License
MIT License