Home › Forums › JavaScript › Touch device ghost click triggers on different element
- This topic is empty.
-
AuthorPosts
-
December 26, 2016 at 4:05 am #249338
Shikkediel
ParticipantAnyone happened to come across the following on other OSes than Android 2.3?
Instead of listening to clicks on touch devices, to avoid the 300ms delay one can program their own sequence of a
touchstart
>touchend
and use that as if they were clicks. So far so good.But in my test case, I’m also listening to
mousedown
andmouseup
to achieve something similar on desktop.The touch events will also automatically emulate these mouse events (including a click) though so some filtering has to take place there to avoid firing any functions twice.
But here it comes… say I have a button that I click to show a modal. This modal will end up on top of the button after applying a jQuery
show()
. Now what happens on Android 2.3 is that the emulated “ghost” events aren’t fired on the original element that was clicked but on the modal!Same thing takes place when hiding that model, clicks to close it will trigger events on any element that is underneath the spot that was originally clicked…
I have managed a workaround but is this familiar to anyone? If so, I’d have to expand the browsers I’m targeting with the code.
Thanks in advance.
$(document).on('mousedown touchstart', subject, function(e) { if (e.type == 'mousedown' && e.which != 1) return; var mean = $(e.currentTarget); mean.one('mouseup touchend', function(e) { if (e.type == 'touchend') $('body').addClass('punch'); else if ($('body').hasClass('punch')) return; mean.trigger('page:tap'); }); }) .on('click', subject, function() { $('body').removeClass('punch'); return false; });
Including the code for completeness. I’m triggering a custom
page:tap
event there on any elements I’ve added to the variablesubject
but that’s not all that relevant. The workaround there is to add a class tobody
while a touch event takes place and remove that class when the click is emulated. Rather circumventive.December 26, 2016 at 5:00 am #249339Shikkediel
ParticipantNever mind that I could leave out the touch events altogether of course. I’d rather handle them… good practice too. And it’s quicker. Although that OS is giving me minor fits on occasion. I almost think they released it before they were really finished.
:-S
But if code works on that, it’ll work on pretty much anything. Probably my toaster too.
December 26, 2016 at 8:38 am #249349Shikkediel
ParticipantLet me summarise what happens, touch events can be a bit mind boggling. And their behaviour isn’t completely predicatable either.
touchstart
andtouchend
take placepage:tap
gets triggered- modal opens
mousedown
,mouseup
andclick
are emulated at same position- modal is now there, events get triggered on it
I’ll be using the aforementioned code, it’ll work for all touch devices (with some minimal adjustment for devices that can also handle a mouse). This will prevent another
page:tap
happening on the modal because of the emulated events. But it’s still strange the events aren’t triggered on the original element.December 29, 2016 at 11:23 am #249453Dendiwiguna
ParticipantThank you for the information
March 14, 2017 at 3:53 pm #252780Shikkediel
ParticipantApparently needed a small adaptation…
$(document).on('mousedown touchstart', subject, function(e) { if (e.type == 'mousedown' && e.which != 1) return; var mean = $(e.currentTarget); mean.one('mouseup touchend', function(e) { if (e.type == 'touchend' && !$('body').hasClass('punch')) $('body').addClass('punch'); else if ($('body').hasClass('punch')) return; mean.trigger('page:tap'); }); }) .on('click', subject, function() { $('body').removeClass('punch'); return false; });
Couldn’t find out why multiple
touchend
events were firing but this prevents triggering all but one.March 14, 2017 at 3:56 pm #252781Shikkediel
ParticipantMarch 15, 2017 at 2:19 pm #252826Shikkediel
ParticipantNever mind then, I’ll try posting the update again:
$(document).on('mousedown touchstart', subject, function(e) { if (e.type == 'mousedown' && e.which != 1) return; var mean = $(e.currentTarget); mean.one('mouseup touchend', function(e) { if (e.type == 'touchend' && !$('body').hasClass('punch')) $('body').addClass('punch'); else if ($('body').hasClass('punch')) return; mean.trigger('page:tap'); }); }) .on('click', subject, function() { $('body').removeClass('punch'); return false; });
I think the ghost clicks might have been inexplicable extra
touchend
events.
This will make sure only a single one is actively triggering.March 17, 2017 at 11:11 am #252882Shikkediel
ParticipantI think the ghost clicks might have been inexplicable extra touchend events.
Nope, the original issue still persists but the custom event itself’s working a lot smoother now.
March 18, 2017 at 11:39 am #252907Mottie
MemberI’ve heard that this solution from Stackoverflow takes care of the ghost click:
$(document).on('touchstart click', '.myBtn', function(event){ if(event.handled === false) return event.stopPropagation(); event.preventDefault(); event.handled = true; // Do your magic here });
Additionally, you might want to consider using pointer events… jQuery’s polyfill PEP is useful in this case. Check out the Why pointer events section in the readme!
March 18, 2017 at 4:59 pm #252913Shikkediel
ParticipantThanks, some interesting reading material there. Much appreciated.
I can’t see how that code could work without
event.handled
ever being set tofalse
though. Unless I’m unaware of a native.handled
that automatcially resets the variableafter clicksomehow…This whole thing is a lot more complicated than it might look at first, I found out.
Pointer events are definitely something I’ll be looking into.March 18, 2017 at 5:07 pm #252914Shikkediel
ParticipantI think I like your approach better by the way, it is similar to what I’ve fiddled together. But instead of using a timeout, I might check the event type and flip the variable on the basis of that.
My old Android phone is utterly slow and I noticed it can take up to 500ms to handle eveything from the
touchend
to the finalclick
. Especially when Google play starts looking for updates simultaneously.March 18, 2017 at 7:13 pm #252915Shikkediel
ParticipantOkay, I think I finally got to the bottom of this. There are two issues crossing each other. The first one is the more generally known ghost clicks, these seem to be handled nicely from where I got so far. The other issue is very Android specific and hard to describe. After a custom (click) event that deals with both touch and mouse and then hiding the element in question, the emulated mouse events caused by touch devices are triggered on the element that was underneath it. And not on the element itself. This is clearly a bug that needs an approach of it’s own – I chose to use
elementFromPoint
to determine which element is underneath and temporarily disable pointer events on it. Strangely enough links still get highlighted because of an emulatedhover
effect now and then but I can live with that.So I’m sending some extra data with the custom
page:tap
event (did I ever mention how excellent this part of jQuery is?), namely the touch coordinates:var nub = $('html'), swiftClick = function(aim) { $(bud).on('mousedown touchstart', aim, function(e) { if (e.type == 'mousedown' && e.which != 1) return; var mean = $(e.currentTarget); if (e.type == 'touchstart') { var feel = e.originalEvent.touches[0], region = [feel.screenX, feel.screenY]; } mean.one('mouseup touchend', function(e) { var hit = nub.hasClass('punch'); if (e.type == 'touchend' && !hit) nub.addClass('punch'); else if (hit) return; mean.trigger({type: 'page:tap', put: region}); }); }).on('click', aim, function() { if (!nub.hasClass('punch')) return; nub.removeClass('punch'); return false; }); }
Also troublesome on Android, nothing but
screenX
andscreenY
did the trick.Next step is to listen to the custom event, check the coordinates and “silence” the element below if it is a link (closing the modal would trigger a redirect before):
var goal = '#anelement'; swiftClick(goal); $(goal).on('page:tap', function(e) { if (!e.put) return; var below = document.elementFromPoint(e.put[0], e.put[1]), sample = $(below).closest('a'); if (!sample) return; sample.addClass('silent'); setTimeout(function() { sample.removeClass('silent'); }, 500); });
.silent { pointer-events: none; }
Very circumventive but that’s old Android for ya…
March 18, 2017 at 8:38 pm #252916Shikkediel
ParticipantSmall addendum. By adding the
punch
class tohtml
orbody
, the Android issue is pretty much resolved because all elements listen to the samepage:tap
(event on the element below will then not get triggered). Adding the class to the element itself would not work in that case. The extra bit of code that silences the element below is mostly effective now for not getting the link underneath hightlighted because of an emulated hover effect. Which I think occurs withmouseup
, meaning that specific emulated event sometimes takes place more than 500ms after the initialtouchstart
… slightly annoying but otherwise to no real effect.March 18, 2017 at 11:46 pm #252918Mottie
MemberI think the
event.handled
works in the script I posted because the event is provided by a delegated binding, so the event is propagating up the DOM tree, so theevent.handled
flag remains.Another idea, used by hammer-time (also the makers of hammer.js), is to add a
touch-action:none;
css property to the element – which does appear to be supported by all modern browsers now. Their script adds the style in-line, but I don’t see why you couldn’t toggle a class name to apply it.March 19, 2017 at 12:06 am #252920Shikkediel
ParticipantI think the event.handled works in the script I posted because the event is provided by a delegated binding, so the event is propagating up the DOM tree, so the event.handled flag remains.
That is an interesting suggestion but I do not see how the code could ever set the variable to
false
. It starts as undefined and after the first event becomestrue
indefinitely. So the initial condition can never be fulfilled. Unless I’m missing something…Edit – seems to always be
undefined
: -
AuthorPosts
- The forum ‘JavaScript’ is closed to new topics and replies.