Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect touchend event outside of an element

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).

like image 553
Mister Smith Avatar asked Jul 15 '13 08:07

Mister Smith


People also ask

What is a Touchend event?

The touchend event occurs when the user removes the finger from an element. Note: The touchend event will only work on devices with a touch screen. Tip: Other events related to the touchend event are: touchstart - occurs when the user touches an element.

Does touch trigger click event?

Because mobile browsers should also work with with web applications that were build for mouse devices, touch devices also fire classic mouse events like mousedown or click . When a user follows a link on a touch device, the following events will be fired in sequence: touchstart.

How do I use Touchmove event?

The touchmove event occurs when the user moves the finger across the screen. The touchmove event will be triggered once for each movement, and will continue to be triggered until the finger is released. Note: The touchmove event will only work on devices with a touch screen.

What is Touchend in Javascript?

The touchend event fires when one or more touch points are removed from the touch surface.


1 Answers

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).

like image 120
Mister Smith Avatar answered Oct 01 '22 23:10

Mister Smith