I've successfully implemented native HTML5 Drag and Drop for moving HTML elements inside a page (just, say, a div from one place to another, nothing related to interacting with the host OS' files whatsoever). This works fine in Chrome and Safari on a PC but I can't start a drag operation in my iPad's Safari.
I've found this so far:
Using Drag and Drop From JavaScript
Safari, Dashboard, and WebKit-based applications include support for customizing the behavior of drag and drop operations within your HTML pages.
Note: This technology is supported only on desktop versions of Safari. For iPhone OS, use DOM Touch, described in Handling Events (part of Safari Web Content Guide) and Safari DOM Additions Reference.
Here. But it's outdated (2009-06-08).
Doe's anyone know if it is possible to use native HTML5 in Mobile Safari? (I don't want javascript-framework like solutions like jQuery UI).
Thanks!
HTML Drag and Drop interfaces enable applications to use drag-and-drop features in browsers. The user may select draggable elements with a mouse, drag those elements to a droppable element, and drop them by releasing the mouse button.
Complete HTML/CSS Course 2022 Now HTML 5 came up with a Drag and Drop (DnD) API that brings native DnD support to the browser making it much easier to code up. HTML 5 DnD is supported by all the major browsers like Chrome, Firefox 3.5 and Safari 4 etc.
I found this incredibly useful javascript code that adds the iOS event shims for you:
https://github.com/timruffles/ios-html5-drag-drop-shim
Complete code just in case the code is deleted sometime in the future (it happens too often):
(function(doc) {
log = noop; // noOp, remove this line to enable debugging
var coordinateSystemForElementFromPoint;
function main(config) {
config = config || {};
coordinateSystemForElementFromPoint = navigator.userAgent.match(/OS 5(?:_\d+)+ like Mac/) ? "client" : "page";
var div = doc.createElement('div');
var dragDiv = 'draggable' in div;
var evts = 'ondragstart' in div && 'ondrop' in div;
var needsPatch = !(dragDiv || evts) || /iPad|iPhone|iPod/.test(navigator.userAgent);
log((needsPatch ? "" : "not ") + "patching html5 drag drop");
if(!needsPatch) return;
if(!config.enableEnterLeave) {
DragDrop.prototype.synthesizeEnterLeave = noop;
}
doc.addEventListener("touchstart", touchstart);
}
function DragDrop(event, el) {
this.touchPositions = {};
this.dragData = {};
this.el = el || event.target
event.preventDefault();
log("dragstart");
this.dispatchDragStart()
this.elTranslation = readTransform(this.el);
this.listen()
}
DragDrop.prototype = {
listen: function() {
var move = onEvt(doc, "touchmove", this.move, this);
var end = onEvt(doc, "touchend", ontouchend, this);
var cancel = onEvt(doc, "touchcancel", cleanup, this);
function ontouchend(event) {
this.dragend(event, event.target);
cleanup();
}
function cleanup() {
log("cleanup");
this.touchPositions = {};
this.el = this.dragData = null;
return [move, end, cancel].forEach(function(handler) {
return handler.off();
});
}
},
move: function(event) {
var deltas = { x: [], y: [] };
[].forEach.call(event.changedTouches,function(touch, index) {
var lastPosition = this.touchPositions[index];
if (lastPosition) {
deltas.x.push(touch.pageX - lastPosition.x);
deltas.y.push(touch.pageY - lastPosition.y);
} else {
this.touchPositions[index] = lastPosition = {};
}
lastPosition.x = touch.pageX;
lastPosition.y = touch.pageY;
}.bind(this))
this.elTranslation.x += average(deltas.x);
this.elTranslation.y += average(deltas.y);
this.el.style["z-index"] = "999999";
this.el.style["pointer-events"] = "none";
writeTransform(this.el, this.elTranslation.x, this.elTranslation.y);
this.synthesizeEnterLeave(event);
},
synthesizeEnterLeave: function(event) {
var target = elementFromTouchEvent(this.el,event)
if (target != this.lastEnter) {
if (this.lastEnter) {
this.dispatchLeave();
}
this.lastEnter = target;
this.dispatchEnter();
}
},
dragend: function(event) {
// we'll dispatch drop if there's a target, then dragEnd. If drop isn't fired
// or isn't cancelled, we'll snap back
// drop comes first http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
log("dragend");
if (this.lastEnter) {
this.dispatchLeave();
}
var target = elementFromTouchEvent(this.el,event)
if (target) {
log("found drop target " + target.tagName);
this.dispatchDrop(target)
} else {
log("no drop target, scheduling snapBack")
once(doc, "dragend", this.snapBack, this);
}
var dragendEvt = doc.createEvent("Event");
dragendEvt.initEvent("dragend", true, true);
this.el.dispatchEvent(dragendEvt);
},
dispatchDrop: function(target) {
var snapBack = true;
var dropEvt = doc.createEvent("Event");
dropEvt.initEvent("drop", true, true);
dropEvt.dataTransfer = {
getData: function(type) {
return this.dragData[type];
}.bind(this)
};
dropEvt.preventDefault = function() {
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=14638 - if we don't cancel it, we'll snap back
this.el.style["z-index"] = "";
this.el.style["pointer-events"] = "auto";
snapBack = false;
writeTransform(this.el, 0, 0);
}.bind(this);
once(doc, "drop", function() {
log("drop event not canceled");
if (snapBack) this.snapBack()
},this);
target.dispatchEvent(dropEvt);
},
dispatchEnter: function() {
var enterEvt = doc.createEvent("Event");
enterEvt.initEvent("dragenter", true, true);
enterEvt.dataTransfer = {
getData: function(type) {
return this.dragData[type];
}.bind(this)
};
this.lastEnter.dispatchEvent(enterEvt);
},
dispatchLeave: function() {
var leaveEvt = doc.createEvent("Event");
leaveEvt.initEvent("dragleave", true, true);
leaveEvt.dataTransfer = {
getData: function(type) {
return this.dragData[type];
}.bind(this)
};
this.lastEnter.dispatchEvent(leaveEvt);
this.lastEnter = null;
},
snapBack: function() {
once(this.el, "webkitTransitionEnd", function() {
this.el.style["pointer-events"] = "auto";
this.el.style["z-index"] = "";
this.el.style["-webkit-transition"] = "none";
},this);
setTimeout(function() {
this.el.style["-webkit-transition"] = "all 0.2s";
writeTransform(this.el, 0, 0)
}.bind(this));
},
dispatchDragStart: function() {
var evt = doc.createEvent("Event");
evt.initEvent("dragstart", true, true);
evt.dataTransfer = {
setData: function(type, val) {
this.dragData[type] = val;
return val;
}.bind(this),
dropEffect: "move"
};
this.el.dispatchEvent(evt);
}
}
// event listeners
function touchstart(evt) {
var el = evt.target;
do {
if (el.hasAttribute("draggable")) {
evt.preventDefault();
new DragDrop(evt,el);
}
} while((el = el.parentNode) && el !== doc.body)
}
// DOM helpers
function elementFromTouchEvent(el,event) {
var touch = event.changedTouches[0];
var target = doc.elementFromPoint(
touch[coordinateSystemForElementFromPoint + "X"],
touch[coordinateSystemForElementFromPoint + "Y"]
);
return target
}
function readTransform(el) {
var transform = el.style["-webkit-transform"];
var x = 0
var y = 0
var match = /translate\(\s*(\d+)[^,]*,\D*(\d+)/.exec(transform)
if(match) {
x = parseInt(match[1],10)
y = parseInt(match[2],10)
}
return { x: x, y: y };
}
function writeTransform(el, x, y) {
var transform = el.style["-webkit-transform"].replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g, '');
el.style["-webkit-transform"] = transform + " translate(" + x + "px," + y + "px)";
}
function onEvt(el, event, handler, context) {
if(context) handler = handler.bind(context)
el.addEventListener(event, handler);
return {
off: function() {
return el.removeEventListener(event, handler);
}
};
}
function once(el, event, handler, context) {
if(context) handler = handler.bind(context)
function listener(evt) {
handler(evt);
return el.removeEventListener(event,listener);
}
return el.addEventListener(event,listener);
}
// general helpers
function log(msg) {
console.log(msg);
}
function average(arr) {
if (arr.length === 0) return 0;
return arr.reduce((function(s, v) {
return v + s;
}), 0) / arr.length;
}
function noop() {}
main(window.iosDragDropShim);
})(document);
Touch events do work now on Safari too (including Mobile). This article has nice code snippets that you can start from:
http://www.html5rocks.com/en/mobile/touch/
I've just been trying to get native drag&drop working in ios safari (ipad pro with 14.0.1), and I'm updating this answer as it kept coming up as my first google result (and no other q&a seems to be up to date).
Following https://developer.apple.com/library/archive/documentation/AppleApplications/Conceptual/SafariJSProgTopics/DragAndDrop.html I have found native html5 drag&drop now works in safari, with a few specific notes;
You MUST set some data in OnDragStart's event; (the effect settings seem to be optional) Event.dataTransfer.setData('text/plain', 'hello');
you MUST Event.preventDefault();
in OnDrop
otherwise safari will load the data you added (unless that's your intention)
OnDragOver
event handler needs Event.preventDefault();
otherwise drop fails (unless intended, as docuemnted)
On ios safari, if you set OnDragStart
's Event.dataTransfer.dropEffect='copy'
and in OnDragOver
's Event.dataTransfer.dropEffect='link'
the drop fails (no OnDrop()
); Seems to be the only combination that fails
I thought you required
-webkit-user-drag: Element;
and `-webkit-user-drop: element;
or if you preferElement.style.setProperty('webkitUserDrag','element');
Element.style.setProperty('webkitUserDrop','element');
but it seems just the usual draggable
attribute is enough
Element.setAttribute('draggable',true);
This seems to be the bare minimum to drag & drop an element
Element.setAttribute('draggable',true);
function OnDragOver(Event)
{
Element.setAttribute('DragOver',true);
Event.stopPropagation(); // let child accept and don't pass up to parent element
Event.preventDefault(); // ios to accept drop
Event.dataTransfer.dropEffect = 'copy';// move has no icon? adding copy shows +
}
function OnDragLeave(Event)
{
Element.removeAttribute('DragOver');
}
function OnDrop(Event)
{
Element.removeAttribute('DragOver');
Event.preventDefault(); // dont let page attempt to load our data
Event.stopPropagation();
}
function OnDragStart(Event)
{
Event.stopPropagation(); // let child take the drag
Event.dataTransfer.dropEffect = 'move';
Event.dataTransfer.setData('text/plain', 'hello');
}
Element.addEventListener('dragstart',OnDragStart);
Element.addEventListener('drop',OnDrop);
Element.addEventListener('dragover',OnDragOver);
Element.addEventListener('dragleave',OnDragLeave);
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