jQuery Drag and Drop on touch devices (iPad, Android)
Asked Answered
D

8

30

We have a card game website that makes extensive use of jQuery Draggable & Droppable and which has worked nearly flawlessly (when using a mouse) for almost a year.

We would REALLY like to have the site work on touch screen devices, but we cannot seem to get jQuery's drag and especially drop functionality to work reliably.

Dragging works "ok" unless the div being dragged is inside another dom element with any kind of offset, margin, padding, etc. If it is, the dragged element is also offset from the user's finger by a similar amount. May not sound like a big deal, but it makes the interface unusuable.

Dropping just doesn't seem to work.

We've researched various options presented here on SO (will try to update this post with links to some of them if I can), but none work for us.

We've also researched jQuery Mobile but this is still in alpha and even so seems to be more of a framework for making a site emulate the UI of a phone vs what we're looking for.

Most of the SO and Google posts on this topic seem to trail off in late 2010 which makes me think there is an obvious answer that maybe we're just missing.

BTW, the functionality we're looking for is clearly technically possible because the YUI libraries for drag and drop work as expected. Unfortunatly, we can't justtify refactoring the site to switch from jQuery to YUI.

Anyone out there have any ideas? We would settle for a answer that supports only iPad, but it really needs to not require we refactor the existing site.

Thanks!

Devoir answered 26/4, 2011 at 20:33 Comment(4)
possible duplicate of How can I make a jQuery UI 'draggable()' div draggable for touchscreen?Twin
This is indeed one of the SO posts we looked into but the droppable fix referenced in the post did not work for us.Devoir
Another SO post we looked at was; #4756005Devoir
rather than just describing your problem it would be helpful to see an example. I recommend creating an example using jsfiddle.net and then we can see if we can help you with the problem.Twin
M
36

Paste this at the beginning of your .js file:

(function ($) {
    // Detect touch support
    $.support.touch = 'ontouchend' in document;
    // Ignore browsers without touch support
    if (!$.support.touch) {
    return;
    }
    var mouseProto = $.ui.mouse.prototype,
        _mouseInit = mouseProto._mouseInit,
        touchHandled;

    function simulateMouseEvent (event, simulatedType) { //use this function to simulate mouse event
    // Ignore multi-touch events
        if (event.originalEvent.touches.length > 1) {
        return;
        }
    event.preventDefault(); //use this to prevent scrolling during ui use

    var touch = event.originalEvent.changedTouches[0],
        simulatedEvent = document.createEvent('MouseEvents');
    // Initialize the simulated mouse event using the touch event's coordinates
    simulatedEvent.initMouseEvent(
        simulatedType,    // type
        true,             // bubbles                    
        true,             // cancelable                 
        window,           // view                       
        1,                // detail                     
        touch.screenX,    // screenX                    
        touch.screenY,    // screenY                    
        touch.clientX,    // clientX                    
        touch.clientY,    // clientY                    
        false,            // ctrlKey                    
        false,            // altKey                     
        false,            // shiftKey                   
        false,            // metaKey                    
        0,                // button                     
        null              // relatedTarget              
        );

    // Dispatch the simulated event to the target element
    event.target.dispatchEvent(simulatedEvent);
    }
    mouseProto._touchStart = function (event) {
    var self = this;
    // Ignore the event if another widget is already being handled
    if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
        return;
        }
    // Set the flag to prevent other widgets from inheriting the touch event
    touchHandled = true;
    // Track movement to determine if interaction was a click
    self._touchMoved = false;
    // Simulate the mouseover event
    simulateMouseEvent(event, 'mouseover');
    // Simulate the mousemove event
    simulateMouseEvent(event, 'mousemove');
    // Simulate the mousedown event
    simulateMouseEvent(event, 'mousedown');
    };

    mouseProto._touchMove = function (event) {
    // Ignore event if not handled
    if (!touchHandled) {
        return;
        }
    // Interaction was not a click
    this._touchMoved = true;
    // Simulate the mousemove event
    simulateMouseEvent(event, 'mousemove');
    };
    mouseProto._touchEnd = function (event) {
    // Ignore event if not handled
    if (!touchHandled) {
        return;
    }
    // Simulate the mouseup event
    simulateMouseEvent(event, 'mouseup');
    // Simulate the mouseout event
    simulateMouseEvent(event, 'mouseout');
    // If the touch interaction did not move, it should trigger a click
    if (!this._touchMoved) {
      // Simulate the click event
      simulateMouseEvent(event, 'click');
    }
    // Unset the flag to allow other widgets to inherit the touch event
    touchHandled = false;
    };
    mouseProto._mouseInit = function () {
    var self = this;
    // Delegate the touch handlers to the widget's element
    self.element
        .on('touchstart', $.proxy(self, '_touchStart'))
        .on('touchmove', $.proxy(self, '_touchMove'))
        .on('touchend', $.proxy(self, '_touchEnd'));

    // Call the original $.ui.mouse init method
    _mouseInit.call(self);
    };
})(jQuery);

Call me in the morning ;) (that's really arrogant, I didn't write this solution although I wish that I had, I'd reference it if I remember where I found it, if anyone know where this code came from please comment and credit that person)

UPDATE: Here you go: This is where I found this

Maladapted answered 11/1, 2012 at 21:32 Comment(5)
Woorks well on most things except BB w/ trackballMaladapted
Very nice, But can you tell me why this destroys my double click? jquery .dblclick()Example
Probably because of the simulateMouseEvent function triggers event.preventDefault on multitouch events...maybe you could try making an if statement that makes an exception out of your dblclick event?Maladapted
@iurisilvio According to the source this actually is jquery.touch-punch.jsPrimal
Not the best solution: if the element to drag takes the whole viewport, using jQuery UI Touch Punch prevents the document to be scrolled. Can be even a problem with element you want to be only draggable horizontally with a container hiding overflow, as a draggable carousel. In this case, the carousel prevents user to scroll the document up and down. I prefer jQuery Touch (github.com/ajlkn/jquery.touch) which makes draggable possibly touch only on mobile (mouse gesture can be filtered) and does not prevent touch events to propagate (thus, document to be scrolled).Throve
P
16

I suggest jQuery UI Touch Punch. I've tested it on iOS 5 and Android 2.3 and it works great on both.

Panthia answered 13/5, 2012 at 5:3 Comment(3)
but it has a bit of a lag/delay before the dragging starts (tested on Chrome Andriod)Grannia
not working range slider smooth on android device , please help meTruthfunction
Not the best solution: if the element to drag takes the whole viewport, using jQuery UI Touch Punch prevents the document to be scrolled. Can be even a problem with element you want to be only draggable horizontally with a container hiding overflow, as a draggable carousel. In this case, the carousel prevents user to scroll the document up and down. I prefer jQuery Touch (github.com/ajlkn/jquery.touch) which makes draggable possibly touch only on mobile (mouse gesture can be filtered) and does not prevent touch events to propagate (thus, document to be scrolled).Throve
K
4

Old thread I know.......

Problem with the answer of @likwid_t is that it blocks also any input or other element that has to react on 'clicks' (for example inputs), so i wrote this solution. This solution made it possible to use any existing drag and drop library that is based upon mousedown, mousemove and mouseup events on any touch device (or cumputer). This is also a cross-browser solution.

I have tested in on several devices and it works fast (in combination with the drag and drop feature of ThreeDubMedia (see also http://threedubmedia.com/code/event/drag)). It is a jQuery solution so you can use it only with jQuery libs. I have used jQuery 1.5.1 for it because some newer functions don't work properly with IE9 and above (not tested with newer versions of jQuery).

Before you add any drag or drop operation to an event you have to call this function first:

simulateTouchEvents(<object>);

You can also block all components/children for input or to speed up event handling by using the following syntax:

simulateTouchEvents(<object>, true); // ignore events on childs

Here is the code i wrote. I used some nice tricks to speed up evaluating things (see code).

function simulateTouchEvents(oo,bIgnoreChilds)
{
 if( !$(oo)[0] )
  { return false; }

 if( !window.__touchTypes )
 {
   window.__touchTypes  = {touchstart:'mousedown',touchmove:'mousemove',touchend:'mouseup'};
   window.__touchInputs = {INPUT:1,TEXTAREA:1,SELECT:1,OPTION:1,'input':1,'textarea':1,'select':1,'option':1};
 }

$(oo).bind('touchstart touchmove touchend', function(ev)
{
    var bSame = (ev.target == this);
    if( bIgnoreChilds && !bSame )
     { return; }

    var b = (!bSame && ev.target.__ajqmeclk), // Get if object is already tested or input type
        e = ev.originalEvent;
    if( b === true || !e.touches || e.touches.length > 1 || !window.__touchTypes[e.type]  )
     { return; } //allow multi-touch gestures to work

    var oEv = ( !bSame && typeof b != 'boolean')?$(ev.target).data('events'):false,
        b = (!bSame)?(ev.target.__ajqmeclk = oEv?(oEv['click'] || oEv['mousedown'] || oEv['mouseup'] || oEv['mousemove']):false ):false;

    if( b || window.__touchInputs[ev.target.tagName] )
     { return; } //allow default clicks to work (and on inputs)

    // https://developer.mozilla.org/en/DOM/event.initMouseEvent for API
    var touch = e.changedTouches[0], newEvent = document.createEvent("MouseEvent");
    newEvent.initMouseEvent(window.__touchTypes[e.type], true, true, window, 1,
            touch.screenX, touch.screenY,
            touch.clientX, touch.clientY, false,
            false, false, false, 0, null);

    touch.target.dispatchEvent(newEvent);
    e.preventDefault();
    ev.stopImmediatePropagation();
    ev.stopPropagation();
    ev.preventDefault();
});
 return true;
}; 

What it does: At first, it translates single touch events into mouse events. It checks if an event is caused by an element on/in the element that must be dragged around. If it is an input element like input, textarea etc, it skips the translation, or if a standard mouse event is attached to it it will also skip a translation.

Result: Every element on a draggable element is still working.

Happy coding, greetz, Erwin Haantjes

Kong answered 24/9, 2012 at 2:23 Comment(2)
Your code was most helpful. I have used it with FullCalendar but not out of the box. I made it so that mouse is emulated only after you hold for half a second. That was the scrolling still works. I wish you put this code on Github so that I could contribute. I think it could use some improvements as well .. that is "b" for example .. variables are crying for verbose names. :-) But hey, it works! Much appreciated.Czarism
Verbose names are important i know, but i use in this code 'b' for a multi purpose boolean (b from boolean) to check some conditions to make it better readable and to limit temporarily vars. But thanks anyway for your contribution.Kong
C
3

I have created a jQuery plugin based on Erwinus' answer: https://github.com/RushPL/jquery.touch-mouse

Czarism answered 30/11, 2012 at 16:52 Comment(1)
Thanks for your contribution, but got error when click on the link: "Repository temporarily unavailable."Kong
H
0

You can try this plugin but seems to work for iphone, ipod and ipad. Not sure if solves you're problem. Can be a specific ...

http://code.google.com/p/jquery-ui-for-ipad-and-iphone/

But android still looking for a solution.

Let me know if it helps. Regards Ricardo Rodrigues

Hoskinson answered 22/7, 2011 at 15:59 Comment(1)
Found that this solves android problem. But still is a problem in older version ..for exemple 2.1 android HTC Hero. Guess to slow.Hoskinson
P
0

LikWidT has the simplest solution hands down from the website referenced. It would be great if any of the JQuery UI programmers could add this code or similar to their package. Jquery is certainly the simplest package to build drag/drop content for web developers... this is unfortunately a major flaw with nearly all devices having touch screens nowdays. I am now coding on a Surface Pro which is opening my eyes to these issues personally.

Just to reference the website again: https://github.com/furf/jquery-ui-touch-punch

Edit: Also to clarify how simple this fix is. I had to first ensure I re-ordered my include files such that JQuery Javascript file was first then Jquery UI Javascript then my CSS Files. Then I just copy/pasted below the include files the Javascript code in the post above and that was it. I modified no code it is simply a function that looks for any touches in real time and converts them to equivalent mouse actions. Hence my above assertions for JQuery coders.

Pitiful answered 9/7, 2015 at 19:22 Comment(0)
C
0

Given worked for me :

eventAfterRender: function (event, element, view ) {
         element.draggable();
},
Chervonets answered 12/9, 2016 at 9:17 Comment(0)
C
0

Tested on HTC One M8 under Android 6.13 / samsung Galaxy tab S2

function simulateMouseEvent (event, simulatedType) { //use this function to simulate mouse event

// restriction to preserve input use
    window.__touchInputs = {INPUT:1,TEXTAREA:1,SELECT:1,OPTION:1,'input':1,'textarea':1,'select':1,'option':1};
    if( window.__touchInputs[event.target.tagName] ) return ;

} 
Closeknit answered 19/1, 2017 at 15:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.