I have an element that is moved (via altering it's leftmargin) relative to a user touch on a mobile device (i.e. dragging it around the screen with your finger).
I have noticed that during a touchmove event (which i believe fires repeatedly for the entire time between touchstart and touchend), the browser does not repaint the window, meaning that the display is not updated until after the user takes their finger off the screen.
I haven't had the opportunity to test this across various devices, so it could only relate to Android devices, or to webkit, or a wider group.
Has anybody come accross this and might there be a workaround to force the browser to redraw during the events duration?
Call event.preventDefault() on the touchstart event.
http://uihacker.blogspot.tw/2011/01/android-touchmove-event-bug.html
Related
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
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.
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.
I am porting a piece of code for an HTML5 app from iOS/Android to Windows Phone 8. In this App there is a vertical scroll view with a number of elements.
In the original App I use "touchstart" and "touchend" events. This means every time the user touches the List I get a touchstart event, and even after scrolling around, when the user lifts the finger I get a touchend.
The MSPointer model works different. There is MSPointerDown which is the exact equivalent of touchstart, so no problem on this end.
I can't get my head around how to model the touchend behaviour though. MSPointerUp is not enough because it is only thrown if the user lifts its finger inside the same container where the MSPointerDown occurred. So if a user touches the list, then swipes it up and then lifts the finger, the event will not be fired. There is also MSPointerOut, which triggers when a user leaves the container where MSPointerUp was fired, the problem here is, it fires as soon as the container is left (so during the scrolling of the list) and doesn't fire after the user lifts its finger.
I'm a bit on a loss here how to model a "touchend" with MSPointer that will trigger in exact the same way as the Webkit one.
Cheers
Tom
This is the way I handle this issue.
Even though the Down/Move listeners are attached to some element, I attach the Up listener to the window.
This means that no matter where on the screen the user lifts the finger/pen/mouse (even outside the browser window in IE on a PC), the Up event is triggered.
This also means that you must handle all Up events in your app in one function. But you can set a flag (some global variable) on an element's Down event, and check for that flag in the UP event.
I've been doing this for a long time for mouse events on PC web pages, to handle when the user let's go of the mouse button outside the browser window.
It's annoying when they don't do this, like in Google Maps Street View where you're trying to pan around far and have to move the mouse a lot, and you let go of the button outside the window but it still thinks you're dragging.
I have a larger problem with swipes not being registered. And I believe its because the SDK's assume you would want to click an drag the entire viewport of Safari instead of any of the divs that could be in it.
How can I prevent this default?
I believe you want to listen for the touchmove event and call event.preventDefault() therein on any elements you don't want to contribute to viewport movement.
jquery example:
$('.interestingElements').on('touchmove', function(event) {
event.preventDefault();
});
In mobile safari, the default behavior for a touchmove involving a single touch is to slide the viewport around.
If two touches are involved, the default behavior is to trigger a gesture event. Preventing default on touchmove prevents the gesture event from ever firing. You can use the changedTouches array to find out how many touches are involved in this touchmove event. Good luck!