Differentiate touch events from mouse events in Firefox in fixed DIV - javascript

I got this custom scrollbar that works fine everywhere, except in that fixed DIV. I'm testing with an Asus laptop with a touch screen (dunno if it matters).
So, on "regular" DIV, when I try to scroll by touching the screen, it triggers a wheel event and it works wonderful. On the fixed DIV, it triggers a mousemove event and I have no idea how to handle it.
I'm guessing that if I can detect if the event is trigger by touch or the mouse, I would be able to do some specific behavior, but the events look exactly the same. I obviously can't do custom behavior for all mousemove events because the user needs to be able to move his mouse without scrolling stuff.
NB: I think the issue came from the fact that the DIV I want to scroll is fixed, but I am not 100% sure (still pretty confident though).

Related

How can I make an entire row clickable? [duplicate]

is there a touch equivalent of the mouseenter.
I would like to detect if user slide on my DIV.
I prefer a solution depending directly on the target element not on a parent element with recounting positions etc.
The site: http://dizzyn.github.io/piano-game/ - works fine with mouse (mouse down and slide; not working with touch slide)
Thank you
2019: Yes-ish: Using pointerenter.
BUT, by default, a touch (or mouse down) causes the element to 'capture' the pointer, preventing further pointerleave/enter events unless you explicitly release the capture.
Moreover, you'll want to set touch-action:none on relevant elements to avoid the browser intercepting touches for default pan/zoom etc.
Example:
CSS:
*{ touch-action: none; }
JS:
let div = document.querySelector("div")
div.addEventListener("pointerdown",(e)=>{
console.log("down")
console.log("attempt release implicit capture")
div.releasePointerCapture(e.pointerId) // <- Important!
})
div.addEventListener("pointerenter",(e)=>{
console.log("enter")
})
div.addEventListener("pointerleave",(e)=>{
console.log("leave")
})
Works in Chrome at least. Not so much in Mobile Safari 13 beta though... According to the w3c specs, I'm fairly certain it should work this way. Maybe when iOS 13 is officially released we'll be in the clear. [I've filed a bug and looks like it's being attended to.]
[Update: iOS 13 issue fixed. Should work in Chrome/FF/Safari]
Look into these events:
touchstart Triggers when the user makes contact with the touch surface and creates a touch point inside the element the event is bound to.
touchmove Triggers when the user moves the touch point across the touch surface.
touchend Triggers when the user removes a touch point from the surface. It fires regardless of whether the touch point is removed while inside the bound-to element, or outside, such as if the user's finger slides out of the element first or even off the edge of the screen.
touchenter Triggers when the touch point enters the bound-to element. This event does not bubble.
touchleave Triggers when the touch point leaves the bound-to element. This event does not bubble.
touchcancel Triggers when the touch point no longer registers on the touch surface. This can occur if the user has moved the touch point outside the browser UI or into a plugin, for example, or if an alert modal pops up.
Specifically touchenter and touchleave.
Source: http://www.javascriptkit.com/javatutors/touchevents.shtml
For anyone who is trying to handle touch events in a web app here is helpful documentation W3C - Touch Events which explains the events in detail and how they are handled.
WC3 states:
If a Web application can process touch events, it can intercept them, and no corresponding mouse events would need to be dispatched by the user agent. If the Web application is not specifically written for touch input devices, it can react to the subsequent mouse events instead.
In short:
You can merely handle mouse events relative to touch events instead of handling both touch and mouse events.
I just wanted to say thank you to the previous poster. His suggestion worked perfectly. And I've been struggling to find a solution to this for weeks. If you're using Svelte within your pointerdown handler function I would suggest using:
const pointerDownHandler = (event) => {
// whatever logic you need
event.target.releasePointerCapture(event.pointerId);
}
He's accurate in saying this part is key. It will not work without it.
Answered this at Touchenter/Touchleave question.
Check please.
https://stackoverflow.com/a/61179966/835753
I will make a shot clarifying for Ian's answer:
Equivalent for mouseenter event is pointerenter event. But it will not work out of the box. To make it work you should:
Add to parent element pointerdown event listener with releasePointerCapture method
div.addEventListener("pointerdown",(e) => e.target.releasePointerCapture(e.pointerId))
Add to parent and to your element touch-action: none CSS property.
Enjoy :)

how to launch a webpage on mobile web application without scroll

EDIT FOR CLARIFICATION:
What I want:
A full screen javascript canvas which can handle touch events without those events being further interpreted by the browser, but also reserve the ability to open a new window on user action.
Examples:
I should be able to swipe my finger around without the webpage trying to scroll
I should be able to swipe my finger around without the contents of the webpage being nudged in any way (normally, when one scrolls to the end of a scroll region, the browser allows some additional spring-loaded buffer scrolling to signal to the user that it is the end of the scroll region).
I should be able to pinch and pan without the webpage zooming
etc...
The point:
I need to interpret these events accurately and in realtime MYSELF to respond to these actions WITHIN THE CANVAS. (I am doing realtime drawing via requestAnimationFrame, allowing me to react to user events without using the DOM)
The state of things currently:
This all works perfectly (except for the ability to open a new window) because I position the canvas to be the full size of the viewport (handling any window resize events), and the canvas listens to ontouchstart, ontouchmove, ontouchend, etc... events, calling evt.preventDefault() after I have handled the user input myself. This works to ensure the canvas is ALWAYS full screen, doesn't budge, and user input is accurately given to me to handle in-game.
The Problem:
One bit of user input I need to handle is the launching of a webpage when they click the region of my canvas with a "launch my webpage" button. However, window.open(mywebpage) doesn't work, because mobile safari only allows such an action in the callstack of a click event. Because I rely on ontouchstart to get responsive controls, and evt.preventDefault() in an ontouchstart event CANCELS the click event from happening, I cannot launch the webpage (it gets blocked by the browser).
My attempted solutions, and why they are insufficient:
Just use a click event rather than ontouchstart: this means I can't prevent scrolling/etc... additionally, it is not as responsive, and doesn't allow me to handle touch-and-drag events well.
Overlay a div (or an a) tag atop the canvas over the launch webpage zone, and add a click event to that: if the user clicks-and-drags starting within this tag, then it allows the page to scroll and zoom. Trying to fix this results in the same problem as before.
ORIGINAL POST:
I have a mobile application that is a full-screen canvas, which locks itself positionally (can't scroll or zoom) so that I can correctly interpret user input uninterrupted (swipes, pans, etc...).
It locks itself in by intercepting touchstart events and calling evt.preventDefault (as well as the meta viewport no-zoom stuff which as far as I can tell doesn't actually do anything?).
This works great, and is absolutely necessary to make a game (or game-like application) function.
The problem is that I also have a "go to this webpage" button. I can intercept the touchstart, and use window.open(somewebpage), but mobile popup blockers will block it. The "rules" seem to be "the webpage will be allowed to be opened iff it is done in the call stack of a user interaction, AND that interaction is a 'click' event".
I have the first part down, but if I change the event to a click event, the web page now interprets swipes as scrolls (and pinches as zooms, etc...). If I have both a click and a touchstart event, then calling evt.preventDefault() on the touchstart (which stops the scroll/zoom) also stops the click event.
If I overlay a div atop the click zone of the "launch webpage" button, then the player can scroll/zoom when their input begins in that button, which results in an unpredictable and wonky experience.
How can I launch another webpage without allowing the current webpage to scroll?
Edit: at request, here is a code snippet at least partially illustrating what I'm trying to do https://jsfiddle.net/phildo/0q8e47fk/10/.
Note that in the "real" case, the canvas takes up the full width/height of the screen, and is explicitly set accordingly on screen resize.
Preventing bounces of any kind on mobile web page is a vast problem through out the mobile devices not depending about the manufacturer. I had similar issue on Windows Phone 8 app years ago and there (quite surprisingly) was a solution dedicated to Windows environment which of course cannot applied here.
For iOS you need an iOS solution, right?
The very solution is named iNoBounce. The idea is to add the little js library to your html page, code with some good conventions and the js lib will do the dirty job of preventing the default when necessary.
The trick it actually does is not to prevent just anything, but the ones only, that are "extra" and will cause the bounce events.
With the words of iNoBounce GitHub Readme:
iNoBounce detects if the browser supports -webkit-overflow-scrolling by checking for the property on a fresh CSSStyleDeclaration. If it does, iNoBounce will listen to touchmove and selectively preventDefault() on move events that don't occur on a child of an element with -webkit-overflow-scrolling: touch set. In addition, iNoBounce will preventDefault() when the user is attemping to scroll past the bounds of a scrollable element, preventing rubberbanding on the element itself (an unavoidable caveat).
The example code asks you to use the following parts (there is a separate example code for canvas, this is only the most common solution):
// All you need is an element with `height` or `max-height`, `overflow: auto` and `-webkit-overflow-scrolling: touch`.
<script src="inobounce.js"></script>
<style>
ul {
height: 115px;
border: 1px solid gray;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
</style>
Source:
[1] https://github.com/lazd/iNoBounce
Edit:
I found out you did not limit yourself to iOS. For other browsers, try
[2] https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior
which introduces overscroll-behavior setting, that you can set to none to disable bounces.
It will work only on Android, not ie or iOS.
For mobile Windows Phone I had the solution like this:
div.wp8ScrollFix {
-ms-touch-action: none;
}
which effectively does the same as iNoBounce, now with single CSS line for the div containing the canvas.
Edit2:
For a search of semi universal solution, I could find that
-touch-action: none;
applied to div element that includes the canvas, you can disable default touch events and for the canvas, define your own.
The solution works on any other than Safari browsers. As in [3] there may be some variants like
-ms-touch-action: none;
but I suppose they are now all same without prefixes. The [3] solution is very old and world has changed a lot from those days.
The sad thing is, the browser support is same at least 2019 [4] and maybe now also.
Sources:
[3] jQuery / HTML5 / gwt app for WP8 (Lumia 920) device: vertical css scroll fix
[4] https://css-tricks.com/almanac/properties/t/touch-action/
Problem
Show a div on top of full screen canvas element that intercepts normal click events on element canvas.
Solution
Aside from click events, you need to intrrcept the following touch events:
touchstart
touchend
touchmove
touchcancel
Additional Info
You only preventDefault on the canvas events so you should still be able to create a clickable/touchable element in the canvas that shows a div outside the canvas positioned with a z-index higher than the canvas element by setting on display: block on the div. The div should also have 100vh and 100vw set foe width and height respectively and be position: fixed. The div should also have a button to hide again display: none.
References
https://stackoverflow.com/a/51127296/806876

Draggable scroll and potential issues on mobile

I have horizontally scrolling panel (via overflow-x:scroll) and user should be able to scroll it by dragging (not just scrollbar, but the content itself too).
Using some draggable carousel library (e.g. owlcarousel) isn't an option as all of them use transforms instead of native scroll.
So my plan is:
bind mousedown event
change horizontal scroll offset on mousemove
stop all actions at mouseup
All is good on desktop. But the problem is mobile, as mobile browsers trigger fake mousedown and mousemove events - the scrolling is corrupted. If I call preventDefault in touchstart/move - fake mouse events stop firing, but pane isn't scrolling either.
Is there any way to prevent fake mousedown/move/up events on mobile without calling e.preventDefault()?
Thanks for any input!
Quoting W3C:
To avoid processing the same interaction twice for touch (once for the touch event, and once for the compatibility mouse events), developers should make sure to cancel the touch event, suppressing the generation of any further mouse or click events. Alternatively, see the InputDeviceCapabilities API for a way to detect mouse events that were generated as a result of touch events.
Unfortunately this doesn't seem to be available in any browser (yet?).
A more viable possibility is to disable touch scrolling on your element, and letting your mouse handling code take care of the scrolling:
.my-panel {
touch-action: none;
}
If this makes scrolling too janky on mobile, a hacky but possibly effective solution might be to ignore any mousemove event that follows shortly (say, within 100 ms) after a touchmove event.

Prevent touchmove default (browser scrolling) until condition is met?

The objective is to have a component, in this case a carousel, that responds to the touchmove event when the user is moving his finger from left to right, but does not prevent the default scrolling up and down, even if the touch originates on the component in question, provided the user moves his finger on the y axis beyond a certain threshold.
Or, more plainly, I need to disable the browser scroll on touch devices, until the touchmove even says its OK, and re-enables it.
Despite several hours of tinkering, I've only been able to disable browser scroll completely or not disable it all. Conditionally disabling does not seem to want to work.
The basic logic is:
Record the coordinates on finger down (touch start).
On touchmove, check to see if the Y difference is greater than 200px.
If so, allow the default of browser scrolling, disable the touchmove listener.
If not, prevent the default of scrolling, scroll left/right.
I've been testing this on a late edition iPad mini. I have discovered the following:
Calling preventDefault() in the touchstart handler stops all browser scrolling for that touch, it cannot be re-enabled during drag.
Trying to conditionally prevent default in the dragmove handler behaves the same as preventDefault in the touchstart handler. That is, it blocks the drag, and ignores the condition!
A condition like this in dragmove:
if(Math.abs(MouseMoveDistanceY) < 200){
if(ev.preventDefault){ev.preventDefault();}
}
...blocks vertical scrolling permanently, even if the threshold gets exceeded!
In any case, how can I, on touch devices and in the dragmove handler, surrender control to the browser scroll when a condition is met? Something like "unPreventDefault()" would be great. Barring that, any ideas?
Just an idea, as I can't give you a direct answer so this is more of just looking at a different approach.. but have you looked at changing the page so that there is nothing to actually scroll when on the touch event? ie/ fix the height and add in overflow hidden.

Android Element Selection Issues

I'm attempting to make my website tablet friendly and I'm facing a strange issue.
I am testing on an Android 4.0 tablet with Chrome 30.
I have a fixed modal popover screen. While this screen is on, we don't want to let the user scroll the background so touch events are prevented. However, in this window we have a scrollable area with overflow:scroll, therefore the touch event is not prevented if the touch start event is detected there. So far so good - Android responsibly scrolls the area as expected.
Problem is, if the user long-presses an element inside the scroll area for about half a second, and only then scrolls - the element where the touch started appears to be selected for a moment. That selection disappears after a bit. But, in case the user scrolls during that bit - the whole page scrolls instead of the scrollable area. It's as if the focus is changed. What's going on?
I tried to set CSS selection rules on the elements inside but it didn't help.
-webkit-touch-callout:none;
-webkit-user-select:none;
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
Every other answer suggests to prevent the touch event which I can't because it's meant to be scrolled. Any ideas what's causing this?
OK, I got it.
Add cursor:default!important to the above CSS rules.
I DID come across such a solution but it failed on first try. In my case, the specificity of elements inside the scrollable area was too strong, rendering cursor:default useless and I didn't realize it. I apply this only to mobile devices by detecting the useragent, therefore I can afford dismissing the previous cursor attribute with !important as it won't affect any desktop clients. Sweet!
Still not sure why this worked. If anyone could supply information on how the cursor attribute affects Chrome on Android I will be grateful.

Categories

Resources