15

On mouseUp or touchEnd I want to get the reference of the element on which that event happened.

Demo

In this example, you need to click on one element, drag mouse and released over another element. When the mouse is released, that element's background will change to red. My code works correctly for mouse events, but not on touch devices.

When the touchStart is on one element and the finger is released over another element, I'm getting only the reference to the first element.

What I need to change to make the touch events work exactly like the mouse events?

var isMouseDown = false;    


var panel1 = document.getElementById( 'panel1' );
var panel2 = document.getElementById( 'panel2' );

panel1.onmousedown = onDocumentMouseDown;
panel1.onmouseup = onDocumentMouseUp;
panel1.onmousemove = onDocumentMouseMove;   
panel1.addEventListener( 'touchstart', onDocumentTouchStart, false );
panel1.addEventListener( 'touchmove', onDocumentTouchMove, false );
panel1.addEventListener( 'touchend', onDocumentTouchEnd, false );

panel2.onmousedown = onDocumentMouseDown;
panel2.onmouseup = onDocumentMouseUp;
panel2.onmousemove = onDocumentMouseMove;
panel2.addEventListener( 'touchstart', onDocumentTouchStart, false );
panel2.addEventListener( 'touchmove', onDocumentTouchMove, false );
panel2.addEventListener( 'touchend', onDocumentTouchEnd, false );



function onDocumentMouseDown(event) {
    event.preventDefault();
    panel1.style.background="#2E442E";
    panel2.style.background="#5D855C";      
}

function onDocumentMouseUp(event) {
    event.preventDefault();
    this.style.background="#ff0000";
}

function onDocumentMouseMove( event ) {

}   

function onDocumentTouchStart( event ) {
    event.preventDefault();
    panel1.style.background="#2E442E";
    panel2.style.background="#5D855C";

}

function onDocumentTouchMove( event ) {     

}

function onDocumentTouchEnd( event ) {
    event.preventDefault();         
    this.style.background="#ff0000";        

}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Sarath
  • 9,030
  • 11
  • 51
  • 84

3 Answers3

13

This was a fun question to tackle. Thanks for that. Here's what I have done. I have modified your touchmove handler as such:

   function onDocumentTouchMove(event) {
    onDocumentTouchMove.x = event.changedTouches[event.changedTouches.length - 1].clientX;
    onDocumentTouchMove.y = event.changedTouches[event.changedTouches.length - 1].clientY;
}

In this handler I'm saving the last co-ordinate that the user moved to. In all probability, this is the point at which the user took his finger, nose, knuckle, stylus, etc off the touch surface. These co-ordinates I then use in the handler for touchend to find the element that surrounds them.

    function onDocumentTouchEnd(event) {
    event.preventDefault();
    var elem = document.elementFromPoint(onDocumentTouchMove.x, onDocumentTouchMove.y);
    elem.style.background = "#ff0000";
}

I have utilized the document.elementFromPoint(mdn link) for this purpose. It is one of those golden yet fairly unknown methods provided to us by the CSSOM spec.

Here's the fiddle.

zeusdeux
  • 501
  • 1
  • 6
  • 14
  • 1
    Great find! I can confirm that this works on a Samsung Galaxy Tab with Chrome. – Justus Romijn Mar 13 '13 at 13:01
  • Also works on opera, stock android browser and chrome on my one x. – zeusdeux Mar 13 '13 at 15:47
  • yup.. i think this is a nice solution.. :) but i dont know whts the support for elementFromPoint.. – Sarath Mar 16 '13 at 20:56
  • 1
    @SarathSaleem [support for elementFromPoint](http://i.imgur.com/6YSudsP.png) from [quirksmode](http://www.quirksmode.org/dom/w3c_cssom.html) – zeusdeux Mar 17 '13 at 20:20
  • Thanks, it worked. It however didn't work on simple taps without hover, so I had to bind `onDocumentTouchMove()` to `touchstart` event as well. – BoltKey Jan 06 '18 at 13:28
0

From what it seems, this is intended behavior for touch events. However, I have a workaround!

I haven't tested this extensively, but it seems to work on mobile Safari for at least touchStart and touchEnd events.

Basically what we are going to do is capture all touch events on the document as they are in capture-phase (see http://www.w3.org/TR/DOM-Level-3-Events/). The handler for these events will find the DOM element at this location and re-send the event with the "correct" target.

It seems that the position of touchEnd events is not well defined, so I had to query the changedEvents array to get the last-touched position. I am not sure if this works as-is for touchMove events et al.

function rerouteTouch (evt) {
    if (evt.passthrough) { // Ignore if already rerouted
        return;
    }
    evt.stopPropagation();
    var newevt = document.createEvent('TouchEvent');
    newevt.initTouchEvent (
        evt.type,
        evt.bubbles,
        evt.cancelable,
        evt.view,
        evt.detail,
        evt.screenX,
        evt.screenY,
        evt.clientX,
        evt.clientY,
        evt.ctrlKey,
        evt.altKey,
        evt.shiftKey,
        evt.metaKey,
        evt.touches,
        evt.targetTouches,
        evt.changedTouches,
        evt.scale,
        evt.rotation);
    newevt.passthrough = true; // Only reroute once

    var target;
    var x = (evt.type === 'touchend') ? evt.changedTouches[0].pageX : evt.pageX;
    var y = (evt.type === 'touchend') ? evt.changedTouches[0].pageY : evt.pageY;

    if (document !== (target = document.elementFromPoint(x, y))) {
        target.dispatchEvent(newevt);
    }
}

document.addEventListener( 'touchstart', rerouteTouch, true); // Third arg is true to indicate capture-phase
document.addEventListener( 'touchend', rerouteTouch, true); 

...

// Your event handlers here

Here's a jsfiddle which works as expected on mobile safari for me: http://jsfiddle.net/DREer/12/

fuzic
  • 2,492
  • 16
  • 22
-1

Perhaps if you can include jQuery into your program you can do something like this:

$('#itemId').on('touchEnd',function(){ this.trigger('mouseUp') });

I'm not sure if jQuery out of the box supports this events, but you can always try jQuery movil, hope it helped

Isaac Gonzalez
  • 1,734
  • 1
  • 16
  • 22
  • @isaac `this` here will still be the element where `touchstart` was fired hence it isn't a viable solution. – zeusdeux Mar 14 '13 at 16:08