How to detect touchend event outside of an element
html, javascript, jquery, mobile, touch
Solution
Finally (almost) got it working in BB10 and Android. I've left a trail of problems in the question updates, so to sum up: the `touchend` event target is the same as the `touchstart`, and it comes without coordinates (the API designer for some reason decided to "hide" them in the `originalEvent` property :) . So I'm retrieving the touch end coordinates from `changedTouches[0].pageX` and `changedTouches[0].pageY` and then obtaining the element where the finger is released using `document.elementFromPoint`. (Before feeding this method with the event coordinates you must add the scroll offset to x and y). Then if the element at that point is the button (or is a child of it), I handle the click.
var $myElement = $("#myelement"); //This is a div and can also contain children.
var startEvent = touchDevice() ? "touchstart" : "mousedown";
var releaseEvent = touchDevice() ? "touchend" : "mouseup";
$myElement.on(startEvent, function(e){
$(this).addClass("pressed");
$(document).on(releaseEvent, function listener(e){
$myElement.removeClass("pressed");
$(document).off(releaseEvent, listener);
var $target = null;
if(e.type === "mouseup"){
$target = $(e.target);
} else {
var x = e.originalEvent.changedTouches[0].pageX - window.pageXOffset;
var y = e.originalEvent.changedTouches[0].pageY - window.pageYOffset;
var target = document.elementFromPoint(x, y);
if(target){
$target = $(target);
}
}
if ($target !== null && ($target.is($myElement) || $target.parents().is($myElement))){
//FIXME
alert("Inside!");
//callback here
return false;
} else {
//FIXME
alert("Outside!");
}
});
return false;
});
Now I've a new problem: in iOS somehow the click events are being duplicated. But this deserves a different question (if not existing already).
Problem
I'm trying to handle a button click. The "button" is a custom div that can also contain children. The click callback should fire when the user has both clicked and released inside the element. If the user clicks inside and then drags and releases outside, the handler should not fire. I need to get this working on both desktop and mobile, hence I'm using `mousedown/mouseup` and `touchstart/touchend` events. I need to change the button class from "pressed" to "normal" even if the user releases outside, so I need to add a listener to capture the releasing event over the document: ``` var $myElement = $("#myelement"); //This is a div and can also contain children. $myElement.on("mousedown touchstart", function(e){ $(this).addClass("pressed"); $(document).on("mouseup touchend", function listener(e){ $myElement.removeClass("pressed"); $(document).off("mouseup touchend", listener); var $target = $(e.target); if ($target.is($myElement) || $target.parents().is($myElement)){ //FIXME alert("Inside!"); //do something here return false; } else { //FIXME alert("Outside!"); //let event bubble } }); return false; }); ``` It works fine with clicks, but it does not work as expected with touch events. I've tested this in Chrome for Android and when pressing over the element, then dragging out, the "Inside!" alert is shown. The problem is in this condition: ``` if ($target.is($myElement) || $target.parents().is($myElement)){ ``` I'm trying to check whether the `touchend` event has occurred in the button or any of the the children. If the button has no children, when clicking on it and then releasing outside, the first part of the OR clause resolves to true. If the button has children and I click in the children, then release in any other part of the screen, the second part of the clause is true. What is wrong with this code? Thanks in Advance. UPDATE I've tested it also in BlackBerry 10 with the same results. Apparently the target for the `touchend` event is the button (or the children), even when I've clicked outside. Is this how it is supposed to work? UPDATE And here's why. This is what the W3C docs say about the event: The target of this event must be the same Element that received the touchstart event when this touch point was placed on the surface, even if the touch point has since moved outside the interactive area of the target element. It makes no sense to me, but anyway this explain my problem. I was assuming the `touchend` event would be called over the element where the finger was released. Would it be possible to detect the finger release outside using a different approach? UPDATE I've tried to get the coordinates of the `touchevent` to get the element there using `document.elementFromPoint`. The problem is this event does not contain the coordinates (the `changedTouches` and `touches` lists are empty). I've thorougly inspected the event in the debugger and there's no way to retrieve coords from it other than the original event's. Then I thought I could cache the last `touchmove` event and get the coords from there, but again this event has no own coordinates! Why on Earth do these two events exist when they are useless? And I've tested this approach in iOS, Android and BB10 with identical results. I've given up. I'll call the callback even if the user clicked outside. I can't believe it is 2013 guys and there's no (simple) way of doing this? I could use the drag&drop api but according to this it is unsupported on mobile (Gotta love mobile web development).