Android browser touchend event bug workaround - javascript

I've been developing a mobile site for my homepage and I have run into an issue when hooking into mobile touchevents. Basically I would like to accomplish the following:
User scrolls down
on touchend event is fired --> a function is called to figure out the amount of the document that is hidden above after the scroll (like jQuery scrollTop)
program takes action based on the amount of the document that is hidden up top
My issues are the following. So touchend works like expected in iOS, when the user lifts her finger the function makes a call to jQuery.scrollTop() which gives me a pixel value for how much the user has scrolled down. However on Android Browser devices it seems that the jQuery.scrollTop() value is calculated on touchstart. That is to say the event doesn't fire off properly, I get the correct pageX & Y coordinates from the touchend event, however scrolltop() returns the value from when the user started scrolling. I've checked around on the inet and this seems to be a known android browser bug, what I want to know is if there a decent workaround for this issue i.e. one that doesn't involve preventing the default scroll behaviour!? Thanks in advance!

Are you taking into account smooth scrolling? or just basic scrolling?
With basic scrolling you should be able to get the correct value simply by using document.body.scrollTop
Let me know if there is an issue

Related

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

How can I use DeviceOrientationControls with scroll in iOS 13?

I am attempting to use the data received from DeviceOrientationEvents to animate (rotate) a camera in three.js using three's DeviceOrientationControls. The controls are updated upon every animation frame, and everything works as I would expect. However, if I begin to scroll, then no DeviceOrientationEvent is fired again until the inertia from the scrolling is complete.
I have confirmed that these events are not fired (or at least not dispatched) during the scroll by logging to the console from within the DeviceOrientationEvent handler. I can see the events fired regularly up until the moment I begin to scroll, then stop, and then resume firing from the moment the inertia from the scroll is complete.
Manually stopping the inertia mid-scroll (by touching the screen) also causes the deviceorientation events to resume.
I have disabled all other scroll event handlers in my script. I have made all touch event handlers passive, have tried making them non-passive as well, and have also tried disabling all touch event handlers in my script altogether.
I am fairly sure by this point that this may be a function of how the processing of the scroll thread (which operates separately to the main thread) and the processing of IMU data are scheduled/queued in the browser, so that there may be no good solution, but I'm asking here in case there is something I've overlooked in my own troubleshooting. This does not appear to be an issue with three.js or the DeviceOrientationControls in three.js, but I've tagged this as three.js just in case anybody has ever come across this problem when attempting something similar.
My unique case for having DeviceOrientationControls enabled while scrolling is that scroll drives the animation of a "camera rig" (of empty objects whose rotation and position are animated), while moving the phone around rotates the camera itself. (It's a bit like being able to turn your head to look around while moving in a railcar.)
My testing has been on an IPhone 11 Pro, with iOS 13.5.1, in Chrome iOS 84, and Safari. I have not tested on Android.
iOS has slowed down repetitive JavaScript functionality during scroll for many years now. This is to conserve battery consumption, since it has to re-render the page lots of times while scrolling, so it halts other secondary commands until scrolling is complete. See here for more.
You could create your own custom scrolling functionality without actually scrolling down an HTML page by capturing vertical swipe gestures via 'touchstart' and 'touchmove'. Or you could use a library like Hammer.js to help you.

Pointer Events on mobile creates new pointerId on every pointerdown

To begin, I have read the documentation that says pointerId conveys no meaning
but to some extent it feels like it does.
When retrieving events from pointerdown on a desktop computer you can see that the pointerId property is always 1. I use this to verify if pointerdown event is coming from a mouse click.
However, when I open the Chrome developer tools and use the responsive mode and change it to a mobile device, let's say Pixel 2, and I click on the screen (which is simulated as a touch event) I get an incrementing pointerId on every click.
Now again, I understand it doesn't have a meaning but why does it stay 1 on desktop but go up infinitely on mobile?
I have inserted a simple snippet which when you run on the page it'll show 1 every click and if, you're on a desktop, click on full page and switch to responsive mode and pick a mobile device, it'll show an incrementing value.
window.addEventListener('pointerdown', (event) => {
console.log(event.pointerId);
});
Update
Thanks to the comments I realize that pointerId increments every time because there could be who knows how many fingers touching the screen of a mobile device so there is no way to differentiate them from others.
Why do I need (want) to keep track of pointerId?
Let's say I'm making an application that on desktop only needs the 1 mouse pointer but on mobile it requires 2 fingers to be used to interact with the application.
I need to keep track of where the left finger has been on the screen and what events it has caused and the same for the right finger. The problem comes where the incrementing pointerId comes in is there is no way for me to tell which finger is touching the screen.
Has anyone come across a solution for a problem like this?

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.

How do I prevent the default behavior of the touchmove event in iOS 5?

I have a web-based application that includes a component that the user can scroll up and down with their finger. I use the event's preventDefault method to prevent the default behavior where the touch move shifts the whole screen around on iOS devices.
Unfortunately this does not seem to work anymore in iOS 5 which I just upgraded to this morning. I have to assume that this is just done differently in iOS 5, but I have yet to be able to find a resource that provides instructions.
Update #1: I haven't been able to find an answer to my specific question, but I was able adjust my code a bit to use the -webkit-overflow-scrolling style (set to a value of "touch") and implement the snazzy inertial scrolling capability (where the content scrolls faster depending on the velocity of your swipe and will "rubber band bounce" back if it hits the boundaries. Pretty cool looking...
Update #2: I have another strange problem now. For some odd reason that overflow scrolling behavior gets mixed up sometimes whereby you have to drag your finger left and right across the containing element in order to make its contents move up and down. I have yet to be able to figure out why this happens - does anyone have any ideas?
I found a very simple solution. When the event hits your element that is allowed to scroll, just flag the event. On the event listener on the document just check if the flag is set and only stop the event if the flag isn't set:
this.$scrollableEl.on('touchmove', function(event){
event.comesFromScrollable = true;
// when you have containers that are srollable but
// doesn't have enough content to scroll sometimes:
// event.comesFromScrollable = el.offsetHeight < el.scrollHeight;
});
$(document).on('touchmove', function(event){
if (!event.comesFromScrollable){
event.preventDefault();
}
});
You could also use event.stopImmediatePropagation instead, so you dont need the eventListener on the document element, but this breaks zepto.js tap in my case:
this.$scrollableEl.on('touchmove', function(event){
event.stopImmediatePropagation();
});
First, I can verify that e.preventDefault() disables all scrolling in iOS 5 using the following code:
document.ontouchmove = function(e){ e.preventDefault(); }
Unfortunately, however, this disables the scrolling on overflow:scroll divs. (If anyone has a solution that leaves the inner element scrolling enabled, please share.)
Regarding update#2, I have noticed strange behavior when there is a scrollable element nested in another scrollable element (including the page itself). Sometimes the device hesitates on which element the user intends to scroll. In particular I've noticed this problem using position:fixed. My solution was to make sure the body has 100% height and that the scrollable elements use position:absolute where possible.

Categories

Resources