Prevent Swipe events when interacting with elements on a page - javascript

I am building an iPad application which is essentially a series of slides.
When I've finished reading a slide I am able to swipe to the next slide *(using Zepto's swipe) which changes the window.location to the next slide. (the swipe event is bound to the window.body as it needs to work on the whole page)...
Here is the problem: some slides have interactive elements such as buttons, draggable items etc. The problem is that the swipe event is triggered when using some of these interactive elements.
Does anyone know of a way to prevent swipe from triggering in these instances? Perhaps settings a sensitivity etc?
I'm stumped...
Best wishes and many thanks!!

The way Zepto manages touch events is it binds listeners to the touchstart, touchend, and touchmove events on document.body. It then performs calculations on what event to send and triggers an event on the element that received the touchstart event. This event then bubbles up through the DOM tree evoking the listeners of each element.
This gives us two ways of preventing swipe events:
First, you could do something like:
$('#my-child-element').bind('touchstart touchend touchup', function(event) {
event.stopPropagation();
});
When your child element receives one a touch event, it will prevent it from propagating to parent elements, most importantly the body tag. This prevents the Zepto touch processor from doing anything, blocking swipe, tap, singleTap, longTap, and doubleTap events from occurring while operating in that element.
Because swipe events also bubble, you could also just prevent those specific events from bubbling to your element that listens to page change swipes:
$('#my-child-element').bind('swipeLeft swipeRight', function(event) {
event.stopPropagation();
});
This will allow you to still receive the Zepto generated events inside your child element but not outside. Zepto tap events will also still work for all elements within your child.
Fiddle here: http://jsfiddle.net/bnickel/dUuUd/

Hope "excludedElements" method will help you, like below.
$(".block").swipe({
swipe: function (event, direction, distance, duration, fingerCount, fingerData) {
},
excludedElements: ".link, a",
threshold: 0
});

Related

How to stop double-touching in iOS from scaling the entire page

I have a webapp with an inner-pane inside an outer-window.
The user can do various touch operations, e.g. zoom-in in the inner-pane via 2-finger pinch, without zooming-in the outer-window, touch move to pan the inner window etc..
On Chrome on Android, the app works as expected.
But Safari on iOS device (iPad), the entire window sometimes scales when quickly double-toching on the border-line of the inner window.
I found out that I can prevent this side effect by adding an event listener on the touchend event.
window.addEventListener('touchend', onTouchEnd5, {capture: false, passive: false});
and calling event.preventDefault() in the binded function.
async function onTouchEnd5( event ) {
console.log('BEG onTouchEnd5');
event.preventDefault();
};
But this causes another side effect where touch-clicks on buttons are not intercepted.
I tried adding eventlisteners on other events that are possibly trigerred by the default behavior, e.g. click, dblclick, touchcancel, fullscreenchange, fullscreenerror, resize, onscroll
The idea was to try and prevent the default behaviour for these events in case that the side effect is caused there.
(the rational was that iphone/ipad can trigger unexpected events).
From the listed events above, the event onscroll is trigerred.
In the binded function to this event, I added preventDefault():
function onScroll5( event ) {
console.log('BEG onScroll5');
event.preventDefault();
};
But the I can still cause the entire window to rescale by quickly double-touching on the border-line of the inner window.
How can I prevent the entire window from scaling in iOS when double touching?
Note:
The scaling of the entire window is the latest observation.
Before that, I found that the entire window scales when e.g. doing a twon-finger pinch in another section of the window.
The solution in this case, was to add preventDefault on another event listener binded function.
Given that there are multiple events in the app, a more general question is:
Is there a way to prevent the entire window from scaling alltogether, in eny event?
Thanks
I've found a bit of a hacky solution.
Make a page called /mobile, or use / and put all your content on a page called /maincontent and use that URL in the next step.
In that page, add an iframe, like this:
<iframe src='/ (or "maincontent")' style='width:100vw;height:100vh;top:0;left:0;position:fixed;border:none;' frameborder='0'></iframe>
I know it's a bit hacky, but it works for me on iOS 14.
I found out the solution.
I had the following DOM structure:
<body>
<div id="div1">
<div id="div2">
<div id="div3">
<canvas>...</canvas>
</div>
</div>
<div id="div4">
<div id="div5"><button></div>
</div>
</div>
</body>
I initially tried to add event.preventDefault() in onTouchStartDiv3(), which listens to events in the canvas element (the closest upstream element with event listener is div3).
This solved the problem of double-touch in the border but caused another problem:
onTouchMoveDiv3() was called immediately (without even moving the finger), which caused other side-effects. (I have some state machine in the level, that depends on the onTouchStartDiv3(), onTouchMoveDiv3(), onTouchEndDiv3()).
I then tried to add event listeners at the window/document levels (onTouchStartWindow(), onTouchStartDocument()) and call event.preventDefault() in them.
This again solved the problem of double-touch in the border but caused another problem:
The button (div5), stopped responding to touch events.
The reason was that, because there is no specific event listener on the button, the event was intercepted higher up, and default behaviour was applied.
Because the window/document event listener called event.preventDefault(), this behaviour was prevented (which stopped the button from responding to touch events).
Finally, I added event listener at the touched border level (div2).
Inside onTouchStartDiv2() I compare event.currentTarget (div2), to event.target where the event originated.
If the event.target is div2 then the event originated from clicking on the border, so I apply event.preventDefault() to prevent the side-effect of resizing the entire image on iOS.
If the event.target is not div2 then the event originated from clicking, e.g. in the canvas, so I do not apply event.preventDefault() to maintain the state machine.
With this logic, I solved the problem:
when touching the canvas element (div3 is the closest upstream element with event listener), the regular behaviour was applied.
when touching the border element (div2), event.preventDefault() was applied to the event listener, and prevent the default behaviour of resizing the entire window (which I wanted to disable).
when touching the button element (div5), the regular behaviour was applied so the button responded to touch events.

How to identify if a mouseover event object came from a touchscreen touch?

On virtually all current browsers (extensive details from patrickhlauke on github, which I summarised in an SO answer, and also some more info from QuirksMode), touchscreen touches trigger mouseover events (sometimes creating an invisible pseudo-cursor that stays where the user touched until they touch elsewhere).
Sometimes this causes undesirable behaviour in cases where touch/click and mouseover are intended to do different things.
From inside a function responding to a mouseover event, that has been passed the event object, is there any way I can check if this was a "real" mouseover from a moving cursor that moved from outside an element to inside it, or if it was caused by this touchscreen behaviour from a touchscreen touch?
The event object looks identical. For example, on chrome, a mouseover event caused by a user touching a touchscreen has type: "mouseover" and nothing I can see that would identify it as touch related.
I had the idea of binding an event to touchstart that alters mouseover events then an event to touchend that removes this alteration. Unfortunately, this doesn't work, because the event order appears to be touchstart → touchend → mouseover → click (I can't attach the normalise-mouseover function to click without messing up other functionality).
I'd expected this question to have been asked before but existing questions don't quite cut it:
How to handle mouseover and mouseleave events in Windows 8.1 Touchscreen is about C# / ASP.Net applications on Windows, not web pages in a browser
JQuery .on(“click”) triggers “mouseover” on touch device is similar but is about jQuery and the answer is a bad approach (guessing a hard-coded list of touchscreen user agents, which would break when new device UAs are created, and which falsely assumes all devices are mouse or touchscreen)
Preventing touch from generating mouseOver and mouseMove events in Android browser is the closest I could find, but it is only about Android, is about preventing not identifying mouseover on touch, and has no answer
Browser handling mouseover event for touch devices causes wrong click event to fire is related, but they're trying to elumate the iOS two-tap interaction pattern, and also the only answer makes that mistake of assuming that touches and mouse/clicks are mutually exclusive.
The best I can think of is to have a touch event that sets some globally accessible variable flag like, say, window.touchedRecently = true; on touchstart but not click, then removes this flag after, say, a 500ms setTimeout. This is an ugly hack though.
Note - we cannot assume that touchscreen devices have no mouse-like roving cursor or visa versa, because there are many devices that use a touchscreen and mouse-like pen that moves a cursor while hovering near the screen, or that use a touchscreen and a mouse (e.g. touchscreen laptops). More details in my answer to How do I detect whether a browser supports mouseover events?.
Note #2 - this is not a jQuery question, my events are coming from Raphael.js paths for which jQuery isn't an option and which give a plain vanilla browser event object. If there is a Raphael-specific solution I'd accept that, but it's very unlikely and a raw-javascript solution would be better.
Given the complexity of the issue, I thought it was worth detailing the issues and edge cases involved in any potential solution.
The issues:
1 - Different implementations of touch events across devices and browsers. What works for some will definitely not work for others. You only need to glance at those patrickhlauke resources to get an idea of how differently the process of tapping a touch-screen is currently handled across devices and browsers.
2 - The event handler gives no clue as to its initial trigger. You are also absolutely right in saying that the event object is identical (certainly in the vast majority of cases) between mouse events dispatched by interaction with a mouse, and mouse events dispatched by a touch interaction.
3 - Any solution to this problem which covers all devices could well be short-lived as the current W3C Recommendations do not go into enough detail on how touch/click events should be handled (https://www.w3.org/TR/touch-events/), so browsers will continue to have different implementations. It also appears that the Touch Events standards document has not changed in the past 5 years, so this isn't going to fix itself soon. https://www.w3.org/standards/history/touch-events
4 - Ideally, solutions should not use timeouts as there is no defined time from touch event to mouse event, and given the spec, there most probably won't be any time soon. Unfortunately, timeouts are almost inevitable as I will explain later.
A future solution:
In the future, the solution will probably be to use Pointer Events instead of mouse / touch events as these give us the pointerType (https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events), but unfortunately we're not there yet in terms of an established standard, and so cross-browser compatibility (https://caniuse.com/#search=pointer%20events) is poor.
How do we solve this at the moment
If we accept that:
You can't detect a touchscreen (http://www.stucox.com/blog/you-cant-detect-a-touchscreen/)
Even if we could, there's still the issue of non-touch events on a touch capable screen
Then we can only use data about the mouse event itself to determine its origin. As we've established, the browser doesn't provide this, so we need to add it ourselves. The only way to do this is using the touch events which are triggered around the same time as the mouse event.
Looking at the patrickhlauke resources again, we can make some statements:
mouseover is always followed by the click events mousedown mouseup and click - always in that order. (Sometimes separated by other events). This is backed up by the W3C recommendations: https://www.w3.org/TR/touch-events/.
For most devices / browsers, the mouseover event is always preceded by either pointerover, its MS counterpart MSPointerOver, or touchstart
The devices / browsers whose event order begins with mouseover have to be ignored. We can't establish that the mouse event was triggered by a touch event before the touch event itself has been triggered.
Given this, we could set a flag during pointerover, MSPointerOver, and touchstart, and remove it during one of the click events. This would work well, except for a handfull of cases:
event.preventDefault is called on one of the touch events - the flag will never be unset as the click events will not be called, and so any future genuine click events on this element would still be marked as a touch event
if the target element is moved during the event. The W3C Recommendations state
If the contents of the document have changed during processing of the
touch events, then the user agent may dispatch the mouse events to a
different target than the touch events.
Unfortunately this means that we will always need to use timeouts. To my knowledge there is no way of either establishing when a touch event has called event.preventDefault, nor understanding when the touch element has been moved within the DOM and the click event triggered on another element.
I think this is a fascinating scenario, so this answer will be amended shortly to contain a recommended code response. For now, I would recommend the answer provided by #ibowankenobi or the answer provided by #Manuel Otto.
What we do know is:
When the user uses no mouse
the mouseover is directly (within 800ms) fired after either a touchend or a
touchstart (if the user tapped and held).
the position of the mouseover and the touchstart/touchend are identical.
When the user uses a mouse/pen
The mouseover is fired before the touch events, even if not, the position of the mouseover will not match the touch events' position 99% of time.
Keeping these points in mind, I made a snippet, which will add a flag triggeredByTouch = true to the event if the listed conditions are met. Additionally you can add this behaviour to other mouse events or set kill = true in order to discard mouseevents triggered by touch completely.
(function (target){
var keep_ms = 1000 // how long to keep the touchevents
var kill = false // wether to kill any mouse events triggered by touch
var touchpoints = []
function registerTouch(e){
var touch = e.touches[0] || e.changedTouches[0]
var point = {x:touch.pageX,y:touch.pageY}
touchpoints.push(point)
setTimeout(function (){
// remove touchpoint from list after keep_ms
touchpoints.splice(touchpoints.indexOf(point),1)
},keep_ms)
}
function handleMouseEvent(e){
for(var i in touchpoints){
//check if mouseevent's position is (almost) identical to any previously registered touch events' positions
if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){
//set flag on event
e.triggeredByTouch = true
//if wanted, kill the event
if(kill){
e.cancel = true
e.returnValue = false
e.cancelBubble = true
e.preventDefault()
e.stopPropagation()
}
return
}
}
}
target.addEventListener('touchstart',registerTouch,true)
target.addEventListener('touchend',registerTouch,true)
// which mouse events to monitor
target.addEventListener('mouseover',handleMouseEvent,true)
//target.addEventListener('click',handleMouseEvent,true) - uncomment or add others if wanted
})(document)
Try it out:
function onMouseOver(e){
console.log('triggered by touch:',e.triggeredByTouch ? 'yes' : 'no')
}
(function (target){
var keep_ms = 1000 // how long to keep the touchevents
var kill = false // wether to kill any mouse events triggered by touch
var touchpoints = []
function registerTouch(e){
var touch = e.touches[0] || e.changedTouches[0]
var point = {x:touch.pageX,y:touch.pageY}
touchpoints.push(point)
setTimeout(function (){
// remove touchpoint from list after keep_ms
touchpoints.splice(touchpoints.indexOf(point),1)
},keep_ms)
}
function handleMouseEvent(e){
for(var i in touchpoints){
//check if mouseevent's position is (almost) identical to any previously registered touch events' positions
if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){
//set flag on event
e.triggeredByTouch = true
//if wanted, kill the event
if(kill){
e.cancel = true
e.returnValue = false
e.cancelBubble = true
e.preventDefault()
e.stopPropagation()
}
return
}
}
}
target.addEventListener('touchstart',registerTouch,true)
target.addEventListener('touchend',registerTouch,true)
// which mouse events to monitor
target.addEventListener('mouseover',handleMouseEvent,true)
//target.addEventListener('click',handleMouseEvent,true) - uncomment or add others if wanted
})(document)
a{
font-family: Helvatica, Arial;
font-size: 21pt;
}
Click me
According to https://www.html5rocks.com/en/mobile/touchandmouse/
For a single click the order of events is:
touchstart
touchmove
touchend
mouseover
mousemove
mousedown
mouseup
click
So you might be able to set some arbitrary boolean isFromTouchEvent = true; in onTouchStart() and isFromTouchEvent = false; in onClick() and check for that inside of onMouseOver(). This doesn't work very well since we're not guaranteed to get all those events in the element that we're trying to listen on.
I usually have couple of general schemes which I use for this, one of them uses a manual principle of setTimeout to trigger a property. I will explain this one here, but first try to reason about using touchstart, touchmove and touchend on touch devices and use mouseover on destop.
As you know, calling event.preventDefault (event has to be not passive for this to work with touchstart) in any of the touchevents will cancel the subsequent mousecalls so you do not need to deal with them. But in case this is not what you want, here is what I use sometimes (I refer as "library" to your dom manipulation library, and "elem" as your element):
with setTimeout
library.select(elem) //select the element
.property("_detectTouch",function(){//add a _detectTouch method that will set a property on the element for an arbitrary time
return function(){
this._touchDetected = true;
clearTimeout(this._timeout);
this._timeout = setTimeout(function(self){
self._touchDetected = false;//set this accordingly, I deal with either touch or desktop so I can make this 10000. Otherwise make it ~400ms. (iOS mouse emulation delay is around 300ms)
},10000,this);
}
}).on("click",function(){
/*some action*/
}).on("mouseover",function(){
if (this._touchDetected) {
/*coming from touch device*/
} else {
/*desktop*/
}
}).on("touchstart",function(){
this._detectTouch();//the property method as described at the beginning
toggleClass(document.body,"lock-scroll",true);//disable scroll on body by overflow-y hidden;
}).on("touchmove",function(){
disableScroll();//if the above overflow-y hidden don't work, another function to disable scroll on iOS.
}).on("touchend",function(){
library.event.preventDefault();//now we call this, if you do this on touchstart chrome will complain (unless not passive)
this._detectTouch();
var touchObj = library.event.tagetTouches && library.event.tagetTouches.length
? library.event.tagetTouches[0]
: library.event.changedTouches[0];
if (elem.contains(document.elementFromPoint(touchObj.clientX,touchObj.clientY))) {//check if we are still on the element.
this.click();//click will never be fired since default prevented, so we call it here. Alternatively add the same function ref to this event.
}
toggleClass(document.body,"lock-scroll",false);//enable scroll
enableScroll();//enableScroll
})
Another option without setTimeout is to think mousover is counter to touchstart and mouseout counter to touchend. So former events (the touch events) will set a property, if the mouse events detect that property then they do not fire and reset the property to its initial value and so on. In that case something along these lines will also do:
without setTimeout
....
.on("mouseover",function(dd,ii){
if (this._touchStarted) {//touch device
this._touchStarted = false;//set it back to false, so that next round it can fire incase touch is not detected.
return;
}
/*desktop*/
})
.on("mouseout",function(dd,ii){//same as above
if(this._touchEnded){
this._touchEnded = false;
return;
}
})
.on("touchstart",function(dd,ii){
this._touchStarted = true;
/*some action*/
})
.on("touchend",function(dd,ii){
library.event.preventDefault();//at this point emulations should not fire at all, but incase they do, we have the attached properties
this._touchEnded = true;
/*some action*/
});
I removed a lot of details but I guess this is the main idea.
You can use modernizr for that! I just tested this on a local development server and it works.
if (Modernizr.touch) {
console.log('Touch Screen');
} else {
console.log('No Touch Screen');
}
So I would start there?
Pointer Events are widely supported now. So now we can use pointerenter and check event.pointerType:
const element = document.getElementById("hoverableElement")
element.addEventListener("pointerenter", (event) => {
if (event.pointerType === "mouse") {
alert("Hovered")
}
})
<div id="hoverableElement">Trigger on hover, but not on touch</div>

jQuery and jQuery Mobile : tap vs touchstart, touchend, touchmove and click?

Does the jQuery Mobile Tap corresponds to adding an event listener to an element like this:
myElement.addEventListener("touchstart", touchStartHandler, false);
If so, what about the remaining normal events such as touchmove, touchend and so on? I mean what is their equivalent in jQuery Mobile?
Thank you for guiding me.
Internally tap makes use of vclick.
If you don't find an event in this list, they aren't exposed with the same name: https://api.jquerymobile.com/category/events/
This means, for example: if you need to handle touchstart, touchend and touchmove, as normally is, you will probably end up to use the set of virtualized mouse event handler: vmousedown, vmousemove, vmouseup and vclick but you may need to handle the status of the pointer (mouse or finger) by yourself. Do not forget to handle vmousecancel.
Moreover, you should note that there is a delay to wait for some events.
Following is a short extract for you, from the jQuery Mobile documentation with some critical concepts for touch devices (mobile or modern hybrid laptops) to pay attention to:
Webkit based browsers synthesize mousedown, mouseup, and click events
roughly 300ms after the touchend event is dispatched.
The jQuery Mobile taphold triggers after 750ms.
After 1500ms, then it is not a touch event. Scroll, TouchMove and
TouchEnd events use this. The block list is cleared.
We recommend using click instead of vclick anytime the action being
triggered has the possibility of changing the content underneath the
point that was touched on screen. This includes page transitions and
other behaviors such as collapse/expand that could result in the
screen shifting or content being completely replaced.
Have a nice day
I dont know if is exactly the same way they do the handle, but both of they wait for an action and then execute funciton.
in jquery mobile you can do
$("p").on("taphold",function(){
$(this).hide();
});
and
$(function(){
$( "div.box" ).bind( "tap", tapHandler );
function tapHandler( event ){
$( event.target ).addClass( "tap" );
}
});
list of events https://api.jquerymobile.com/category/events/

How do we make use of `mouseover` and `mouseout` event?

As JQuery document says, I have converted many lines mouseover to mouseenter because it does not fire more than once.
http://api.jquery.com/mouseover/
mouseover fires when the pointer moves into the child element as well
mouseenter fires only when the pointer moves into the bound element.
Even hover event works as mouseenter and mouseleave, not as mouseover and mouseout.
It makes me wonder why there is mouseover event if mouseenter can do it all.
For me, mouseover gets fired unpredictably when you move mouse around on an element. It seems really dependent on the depth of child elements.
Is there a good use-case of mouseover and mouseout, which needs to fire multiple times?
That I know of, there is no use case for mouseover/mouseout at all. The only reason they exist is because these events are triggered by browsers because they are in the standard DOM event list. mouseenter and mouseleave are not standard events, but they are jQuery-specific constructs.
I suppose a use case would be if you wanted the event to trigger when moving the mouse over and out of the children of the element that the events are bound to. I can't think of anything specific, but at least this functionality is available. If only mouseenter/mouseleave existed, you wouldn't have a choice in the matter.
From http://code.jquery.com/jquery-1.9.1.js:
jQuery.each({
mouseenter: "mouseover",
mouseleave: "mouseout"
}, function( orig, fix ) {
/* content snipped */
Speculation: the reason why the creators of jQuery created the mouseenter and mouseleave non-standard events is because their behavior works as you would expect the mouseover/mouseout events to work (i.e. without regard for descendants).
Because the event contains coordinates of cursor.
So if you need to track mouse coordinates under the target, you have to use 'mouseover'

touchstart and touchend events refering to the same element, while events done on different elements

I am having a problem with my Javascript code for Android tablets.
Suppose I have grid made of several div tags. with class "box";
Now I bind event handlers for all these divs in the grid.
When the touchstart (mousedown) event occurs and I move the cursor to some other div in the grid (without releasing the cursor) and then release the cursor (touchend) on this current div. When I tried to alert the id of this current div (i.e. touchend div), the alert shows the id of the div where the "touchstart" has occurred.
$(".box").bind('touchstart',function () {alert($(this).attr("id"))});
$(".box").bind('touchend',function () {alert($(this).attr("id"))});
Actually this is my first program for the Android tablet. So I need help for this.
This is expected and it would be be very confusing if it did anything else. If the element it ended up on did not have a registered listener you would never get the touchEnd event at all.
You can see where the touch went by looking at the coordinate properties of the touches and you can track it in progress with touchmove.
Don't really get your question. Are you trying to get the alert to display the div id of where you mousedown? You will need to store the id in the touchstart event in a variable and display the value when touchend has occured.

Categories

Resources