How can I use DeviceOrientationControls with scroll in iOS 13? - javascript

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.

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

PIXI - disabled preventDefault touch events not working on Android devices

Since I´m working on a project where I need to be able to drag objects around my canvas but also to scroll the entire page by dragging the actual canvas 'background' behind my PIXI Sprites, i followed the findings of this guy here:
https://github.com/pixijs/pixi.js/issues/2483 :
By default, the Pixi canvas/display-area cannot be used to scroll the
webpage that contains it. Which is important on touch screens. (eg. If
you use the rest of the web-page to pinch-zoom into the Pixi canvas,
you can become trapped and unable to zoom back out (or pan away),
because there's no non-Pixi-canvas area of the page to "grab" with
your pinch gesture).
To enable this functionality, I use autoPreventDefault. But this comes
with some undesirable side-effects, like scroll/pinch-zoom actions
over the canvas registering "taps" or clicks in a way that doesn't
make sense. (ie. I'm attempting to zoom or scroll the outer page at
that point, not interact with the Pixi canvas)
To work around that, I modify and compile my own custom version of
Pixi where I can apply preventDefault in a more granular way...
To get page-scrolling functionality it seems I only need to
preventDefault in the InteractionManager.prototype.onTouchEnd
function. Whereas autoPreventDefault will also preventDefault on 3
other events. (onMouseDown, onTouchMove, onTouchStart).
Leaving autoPreventDefault = false and applying preventDefault only to
onTouchEnd, gives me the functionality I need. But it'd be nice to not
have to customize and rebuild Pixi in this way every release. (Sorry
if there's something else I'm missing here; I don't completely
understand the event system in Pixi, or what else to do about this
scroll-touch problem)
So i disabled e.preventDefault() on 'onTouchStart' and on 'onMouseMove' but left it as is on 'onTouchEnd'
This works perfect on IOS devices but not on Android, the only exception being a Samsung A7 using Adblock browser (fails on Chrome).
Would really appreciate some help on this.
TLDR:
Disabling PIXI´s e.preventDefault on onTouchStart and onMouseMove works on IOS devices and lets me scroll the page by draggin my canvas around but not on Android devices.
My solution for that was to use
renderer.plugins.interaction.autoPreventDefault = false
This should work on iOS and Android.
Docs for autoPreventDefault reads:
Should the manager automatically prevent default browser actions.
Using PIXI 4.5.6.
Take a look at the docs:
http://pixijs.download/dev/docs/PIXI.CanvasRenderer.html#plugins
http://pixijs.download/dev/docs/PIXI.interaction.InteractionManager.html
Using renderer.plugins.interaction.autoPreventDefault=true should do the trick.

Native scroll delaying or stopping JavaScript execution on iOS

This isn't a specific JS code issue, but more the way iOS deals with JS that is causing more problems on my site than most others.
On iOS only (it doesn't happen on Android) if I'm natively scrolling (up/down) and then try to activate some JS just before the scroll has finished (very quickly) then it completely ignores the JS.
I believe that Apple do this so that the UX always remains priority (don't let any crappy JS slow down the user), but in this case it's just a very simple piece of JS that I want to allow to run.
As an example, if a user is scrolling and then quickly presses a tab at the top of the screen that opens a fixed navigation panel then it won't register if the native scroll is still happening. If they press it again (the scroll has finished) then it works.
I'm also using a JS slider to scroll horizontally through images and if I try to scroll left/right just before the native up/down scroll has finished it sort of jumps and isn't good UX. I think it's prioritising the native scroll but still activating the horizontal scroll with some sort of delay.
It's not a massive problem, but not perfect. If everybody slowly navigated the site and waited for the native scroll to come to a complete stop, it would be great. But of course people won't do this.
I don't think preventing the default behaviour will do anything. I have tried to take over the native scroll before on iOS and I just don't think you can.
I think this may actually happen on many sites. I've just tried to find a good example by visiting stackoverflow.com on an iPhone and if you scroll quickly and then quickly hit a link before the scroll has finished it won't register. I don't think text links are as big a UX issue though, but a horizontal slider and big 'open menu' button at the top are much more likely to be hit quickly before the native scroll has ended (as you don't need to read something before you press it, like with text links).
I have various JS scripts on a site that would benefit from this being improved in iOS, so if I can understand a way around it, why it happens, what is going on, then I can apply individual fixes to each of those scripts.
Thanks.
The problem is not that iOS ignores javascript while scrolling (more precisely, while the scroll momentum is active). The problem is that, while that happens, iOS does not really register the position change of elements on the screen. In fact, if you have a handler attached to the scroll event, it will stop firing the moment you stop touching the screen, and then will fire just once when the scrolling stops.
Consequence? You think you're touching a link, but you aren't. The image on the screen has moved up or down, but, to the broswer, everything is on the same position, so, actually, you aren't touching anything (or are touching something different). I got very annoyed when I found this behaviour because, in my case, my page is full of images that are links to a gallery ... and if you touch them while scrolling, the gallery opens showing you not the image you touched, but another (The one that really was on that position when your fingers stopped touching the screen).
Is there a workaround? The only one that I know of is disabling the scroll momentum, but you lose scrolling performance.

Capture horizontal touch movement while allowing native vertical scroll

I'm trying to explore how feasible it is to create an HTML5 interaction where a touch can be conditionally captured by the initial direction of the motion.
The goal is to capture and track the motion of the touch only if it initially moves in a horizontal direction. Combined with a responsive page layout which only scrolls vertically, we should be able to use horizontal swipe motion to do something cool by tracking it.
The problem is in the seeming impossibility (especially on iOS) of performing touch sequence capture conditionally.
In order to track a touch (and by this I mean specifically obtaining the stream of x,y coordinates which represent the position of the finger over time), touchstart has to be preventDefaulted in order to prevent the page from engaging native momentum-scroll. Native momentum-scroll, while in operation, suspends all JS execution (setTimeout, rAF, jQuery animate et al...) and even CSS keyframe/animation/transition execution.
I would really like to know if there's a way to somehow condition the preventDefault() of the original touchstart event. It's completely possible for JS to store that event object, and then not call it until some later time (when it is determined that we are indeed interested in preventing the native scroll from starting).
But this is certain to fail because the default behavior of not running preventDefault() on that event is to engage native-scroll, which then will block JS execution for the entire remaining duration of the scroll. Failing to return the event listener function attached to touchstart does not appear to be an option. It would freeze everything.
The tentative answer, then, without any additional insight, is that all swipes must be captured if we want to be able to capture it at all, and scrolling has to be "faked" via external means à la (iScroll)[http://cubiq.org/iscroll-5-ready-for-beta-test] (personally I would want to explore some sort kind of combination of rAF and window.scrollTo and would be surprised if iScroll 5 does not employ these APIs)
Incidentally the description for the 4th demo there got me really excited, but it turns out that all it does is create a page section that iScrolls horizontally while the rest of the page behaves regularly, which is entirely mundane.

iOS Delay between touchstart and touchmove?

I'm attempting to convert my web app into a form usable by mobile devices. I'm attempting to build in support for touch gestures like horizontal scrolling. I'm finding some strange behavior in my app.
I start a gesture with a touchstart event, and then scroll on touchmove. However, my application sees a 500-700 ms delay between receiving these two events. As far as I can tell, my app is doing no other work between these two events.
Other aspects:
The code is written in jquery, using
$(element).bind(touchmove, function(ev) {return myobject.DoTouch(ev) }
were the DoTouch command simply checks the ev.type, records the touch position, and returns false.
Any ideas what I should look for to try to solve this? The lag between touching and getting a response from the app is very annoying.
Yes. It turns out, this is how iOS works. I was pulling my own hair out for some time. Read more here: http://developer.apple.com/library/ios/#DOCUMENTATION/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html. Essentially, if iOS thinks it can handle this as an internal PAN gesture, it does and doesn't even bother sending a touchmove event at all.
In my project, I found that if the viewer makes the touchmove gesture very deliberately and pauses a bit longer before lifting the finger at the end of the move, then the touchmove event is, in fact, sent as one might expect. So, the documented behaviour may be a little iffy versus reality, which only added to confusion and my debugging efforts.
Anyway, if iOS handles the event internally as a PAN gesture, it will send a scroll event before the touchend. In my project I was able to use this to set the flag I was using to distinguish dragging gestures (which was normally sent in my touchmove handler) and ignore any behaviour in stand-alone touchend handlers that were not related to the handling of my own scroll-handling.
I hope this helps you (and others) as well!

Categories

Resources