How to detect mobile Safari global orientation rotation animation - javascript

Hi I'm trying to trigger an event when mobile Safari rotates to a different orientation. I am aware of the orientationchange however this is not acceptable because it is called after the orientation rotation animation is played and the new orientation is set. I have an element that I need to hide before or during the animation.
I'm trying to capture the state before the orientation has changed particularly before the animation plays. I've tried applying events like webkitAnimationStart and animationstart to the window, document and document.body and none of them seem to be triggered. Hoping I'm overlooking something.

This is a problem occurring in almost every mobile browser as far as I saw and there is no straightforward solution for it.
A semi-official suggestion coming from the Chrome team posted on their blog under Unlock screen on device orientation change is to use deviceorientation and simulate what the browser does internally to figure out the orientation of the device:
var previousDeviceOrientation, currentDeviceOrientation;
window.addEventListener('deviceorientation', function onDeviceOrientationChange(event) {
// event.beta represents a front to back motion of the device and
// event.gamma a left to right motion.
if (Math.abs(event.gamma) > 10 || Math.abs(event.beta) < 10) {
previousDeviceOrientation = currentDeviceOrientation;
currentDeviceOrientation = 'landscape';
return;
}
if (Math.abs(event.gamma) < 10 || Math.abs(event.beta) > 10) {
previousDeviceOrientation = currentDeviceOrientation;
// When device is rotated back to portrait, let's unlock screen orientation.
if (previousDeviceOrientation == 'landscape') {
screen.orientation.unlock();
window.removeEventListener('deviceorientation', onDeviceOrientationChange);
}
}
});
The particular use case the Chrome team used this code for is to get the device's orientation after using screen.orientation.lock (which disable orientation change events).
This can be generalized as a substitute for orientation change events giving you a slight time-advantage before the animation kicks in.
The tricky part is figuring out the right angle range for which the browser decides to switch orientations (you don't want to start your animation when the browser doesn't actually switch orientations).
One way to solve this is to take complete control over orientation changes using screen.orientation.lock where essentially you set the threshold and lock the orientation accordingly.
However since the world isn't perfect, screen.orientation.lock only works in fullscreen mode or in standalone web-apps... If you intend your app to be a fullscreen experience or a standalone web-app then you're in luck.

Related

Why might dragging SVG elements via TouchEvent on iPhone iOS be laggy?

I have created a web interface where the user can drag and drop SVG elements on screen. I am struggling with the performance of moving the SVGs via touch events on iPhone iOS using the webkit engine.
Everything is fine on desktop browsers and on Android phones that I could get hold of, but iOS on iPhones shows very bad performance (seems fine on iOS on one iPad that I could get hold of, but it sometimes leaves some traces of the SVG after moving).
There seems to be a delay before the touchstart event kicks in after touching the device and a delay before the touchend event is triggered after releasing the touch: An audio sample (already loaded) that is supposed to play after picking up or dropping the element plays with a delay of ~1.5 seconds. The touchmove event seems to be handled smoothly though - no delay with moving the SVG (after touchstart has ended).
I have already checked iOS Delay between touchstart and touchmove? - but the site that's linked to doesn't help me. I fail to get the scroll event on any element (window, document, svgElement) - and even if I did, I wouldn't know how this could help me.
I assumed the the issue might be related to the size of the base64 encoded background image that the SVGs are using, but reduzing that size even dramatically didn't help.
I read about some 300-350ms delay that iOS might have if there's no "fast tap" mode set, but a) the delay between touching/releasing the screen and playing the audio is longer than 350ms (rather 1.5 seconds) and b) playing with the touch-action CSS property did not help. (Eliminate 300ms delay on click events in mobile Safari)
I am really not sure if I am doing anything wrong (very well possible!) or if the webkit engine on (iPhone) iOS is simply so bad (compared to e.g. Blink on Android that runs flawlessly) that it cannot handle to render/move SVGs? Testing this is particularly iffy, because Browserstack doesn't issue TouchEvents properly and I never succeded to hook up the single physical iOS device that I have (a 2015 iPod Touch) to my Linux machine for remote debugging (while it's very simple for Android on Chromium). I'd really be grateful for hints!
An SVG roughly follows the following pattern (some attributes like viewBox, stroke-width etc. omitted):
<svg>
<defs><pattern id="SOME_ID"><img href="data:SOME_BASE64_ENCODED_IMAGE" /></pattern></defs>
<path fill="url(#SOME_ID)" d="SOME_SIMPLE_PATH"></path>
<path d="SOME_OTHER_SIMPLE_PATH"></path>
</svg>
The SVGs can be moved by MouseEvent or TouchEvent using the following logic:
// this.svgElement is the DOM element within the class
this.svgElement.addEventListener('touchstart', this.handleMoveStarted, false);
this.svgElement.addEventListener('mousedown', this.handleMoveStarted, false);
// Keep track of start position and add move/end listeners
handleMoveStarted(event) {
event.preventDefault();
event.stopPropagation();
if (event.type === 'touchstart') {
this.moveInitialX = event.touches[0].clientX;
this.moveInitialY = event.touches[0].clientY;
this.svgElement.addEventListener('touchmove', this.handleMoved, false);
this.svgElement.addEventListener('touchend', this.handleMoveEnded, false);
}
else {
// Same principle for event.clientX/Y and MouseEvent
}
// Callback to play audio here
}
// Compute delta position and update
handleMoved(event) {
event.preventDefault();
event.stopPropagation();
let deltaX = 0;
let deltaY = 0;
if (event.type === 'touchmove') {
deltaX = this.moveInitialX - event.touches[0].clientX;
deltaY = this.moveInitialY - event.touches[0].clientY;
this.moveInitialX = event.touches[0].clientX;
this.moveInitialY = event.touches[0].clientY;
}
else {
// Same principle for event.clientX/Y and MouseEvent
}
this.svgElement.style.left = `${parseFloat(this.svgElement.style.left) - deltaX}px`;
this.svgElement.style.top = `${parseFloat(this.svgElement.style.top) - deltaY}px`;
}
// Used to remove listeners on tochenend/mouseup
handleMoveEnded(event) {
event.preventDefault();
event.stopPropagation();
this.svgElement.removeEventListener('mousemove', this.handleMoved);
this.svgElement.removeEventListener('touchmove', this.handleMoved);
this.svgElement.removeEventListener('mouseup', this.handleMoveEnded);
this.svgElement.removeEventListener('touchend', this.handleMoveEnded);
// Callback to play audio here
}
I have investigated this issue some more, and it turns out that it's not the SVG dragging that's causing the huge delay in iOS, but it's the callbacks that follow. It seems that iOS has quite some trouble with playing plain HTML5 audio in a timely (real-time) fashion and I'll have to revert to some other solution (HTML 5 audio .play() delay on mobile).

Android's BuiltInZoom doesn't work in hybrid app

I'm developing a hybrid app on Android that displays HTML on WebView.
I want to zoom the screen by pinch-out, but BuiltInZoom doesn't work (can't zoom in/out) when I get a touch event on the JavaScript side.
It occurs with Pixel 4 (Android 10), but it works correctly with Pixel 3 (Android 9).
What I've already tried
If I don't get a touch event in JavaScript, I can zoom in and out.
Since pinch event is called on onScale, I can get current span and apply on onDraw.
However, this is undesirable because it does not reflect the "maximum-scale" of the HTML "viewport". I want to control the maximum magnification according to the "viewport".
I get the touch event as follow.
view.addEventListener('touchstart', (e) => {
console.log('touchstart');
});
view.addEventListener('touchmove', (e) => {
console.log('touchmove');
});
I set BuiltInZoom to enable on Android.
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setDisplayZoomControls(true);
webView.getSettings().setSupportZoom(true);
Note
It occurs with Pixel 4 (Android 10) and Galaxy Tab S4 (Android 9), but it works correctly with Pixel 3 (Android 9).
I can zoom by using zoom controls, but it was already deprecated.
I can zoom after long pressing on screen with one finger.
I can zoom when placing one finger on an element that I don't get touch event.
onScale is called whenever I operate pinch action.
WebViewClient.onScaleChanged is only called when it can be scaled.
I change the "maximum-scale" of the HTML "viewport" dynamically because some pages are allowed to be zoomed and others are not.
I can zoom by ignoring ACTION_POINTER_DOWN in WebView.onTouchEvent.
But suddenly it's zoomed largely because there is no position.
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
return false;
}
super.onTouchEvent(ev);
return true;
}

JavaScript scroll based animation is choppy on mobile

I have 2 divs (left and right) and i want to scroll the left based on the right.
https://jsfiddle.net/3jdsazhg/2/
This works fine on desktop, but when i change to mobile, it's not smooth anymore...
This can be noticed very easily, by changing
_left.style.top = _content.scrollTop - (_content.scrollTop * ratioLeftRight) + 'px';
to
_left.style.top = _content.scrollTop + 'px';
Where it should act as a fixed positioned div
I would like to know the exact reason why this isn't smooth... I know that it's not the animation. Simple animation on the div is smooth, the issue comes up when it's based on scroll.
How can i make this animation smooth?
It's probably choppy because it's being fired ALOT when being scrolled, in fact i'm pretty sure IOS mobile pauses the javascript execution whilst the user is scrolling.
Instead I'd suggest using an interval, you could tweak the time between each interval to what feels good for your use-case.
Although it may seem intensive that it's firing this logic every X millisecond when using the scroll event you could be firing the event off hundreds of times per second, which is going to be far more intensive and noticeable to a user using a device with limit processing power.
(function () {
var interval = null,
//Currently set at 0.4 seconds, play with the code
//and change this value to see what works best for
//this use-case
time_between_interval = 400;
setInterval(scrollLogic, time_between_interval);
function scrollLogic () {
//The function body of what you're assigning
//to the scroll event.
}
//I have omitted clearing the interval but you would want to do that, perhaps on page change, or something.
//clearInterval(interval);
})();
I finally managed to think out a solution.
From my point of view, i'm guessing the mobile view fires the scroll event less often and because we are scrolling the wrapper, we first scroll the whole page and then scroll back with js the left part and because it's different from the desktop version, this issue becomes visible...
The solution was to change the left side to fixed position, and substract from the top instead of adding to it.
_left.style.top = -(_content.scrollTop * ratioLeftRight) + 'px';

Allow vertical scrolling with touchstart when event.preventDefault enabled

I am currently having an issue in allowing vertical scrolling when event.preventdefault is enabled.
I am trying to add swipe functionality to my mobile page, I have tried may frameworks like hammer.js, swipe.js etc, and all of them require event.preventDefault enabled to detect left and right swipes.
When event.preventDefault is enabled, the swipes detect perfectly, however you lose the ability to vertical scroll when you are on that element. i.e you cannot move the screen up or down on a mobile device, when your finger starts on the swipe element.
I have tried building my own little script which works well, but again has the issue of vertical scrolling, that is an issue.
var el = document.getElementById('navigation');
el.ontouchstart = function(e){
e.preventDefault();
el.innerHTML = "touch start";
};
el.ontouchend = function(e){
el.innerHTML = "touch end";
};
el.ontouchmove = function(e){
el.innerHTML = "touch moved";
};
el.ontouchcancel = function(e){
el.innerHTML = "touch cancel";
};
Any ideas???
It's a common issue where you want the native browser behaviour to work alongside the interaction behaviour that people come to expect from a touchscreen device.
If you want to use a library you might need to hack it open as you WILL need to prevent the defaults to prevent the page from jumping all over the place when using touch events, but at other times you want to omit it as you want to prevent the page from remaining in a static position, obscuring other content.
Ideally you want add a clause to the handler that instructs them whether or not to prevent the default behaviour the browser executes upon receiving the event.
Swiping for instance, is a behaviour that should occur in a short time frame (if you are taking for instance one whole second in moving your finger from one area to the other instead of, let's say, 120 ms, you're not actually swiping, but dragging. Thinking about time frames may help you here, for instance:
var threshold = 150, interactionStart;
el.ontouchstart = function( e )
{
// store timestamp of interaction start
interactionStart = +new Date;
};
el.touchmove = function( e )
{
// get elapsed time in ms since start
var delta = +new Date - interactionStart;
if ( delta > threshold )
{
e.preventDefault();
}
else {
// super cool magic here
}
};
Whether 150 ms is the threshold you want depends on the action, as you see there is no fixed answer for your question as the actual implementation depends on what your application needs in terms of touch interactions.
You could also consider not blocking the events default when the user is scrolling more along the vertical axis (i.e. compare whether the delta position of the events Y offset (relative to the start Y offset) is larger than the events X offset (relative to the start X offset) to detect whether the users is moving left or right or up or down (for instance if you have a carousel that can swipe horizontally (where the default behaviour is blocked so the page won't move up/down during the horizontal scroll) but want the page to scroll vertically when the user is obviously dragging the page among the vertical axis).
The library jquery.touchSwipe solves that.
The library: https://github.com/mattbryson/TouchSwipe-Jquery-Plugin
An example page where swiping and scrolling are combined: http://labs.rampinteractive.co.uk/touchSwipe/demos/Page_scrolling.html

Wait To Get Touch Moves, Why?

I am working on a touch based JS application, I've studied Flex and Royal slider as examples. I noticed that both sliders acting similarly when getting touchmove event:
var started,touched ;
el.bind('touchstart',function(e){
started = Number(new Date()) ;
// Get pageX and pageY etc...
}) ;
el.bind('touchmove',function(e){
touched = Number(new Date()) ;
if (started-touched > 500) {
// Handle touch moves etc...
}
}) ;
My JS app works seamless without these, but why do they need to do this? Why they are waiting 500ms to get move datas?
I believe this is some kind of sensitivity setting. You only want to register a touch-move (drag) event if the user has been moving his or her finger across the device for at least 500ms (in this example).
This could be useful to differentiate between taps and drags. Otherwise, if the user would slightly move his/her finger when clicking, for example, a button, the app would also register a drag. As some controls accept both events, this could lead to erroneous behaviour.

Categories