Android's BuiltInZoom doesn't work in hybrid app - javascript

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;
}

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 browsers long hold disable/ mouse up after 1second of mousedown

I am trying to build a one touch game on HTML5 canvas. It's a running game made with the help of this tutorial:
http://blog.sklambert.com/html5-game-tutorial-game-ui-canvas-vs-dom/
I changed the existing controls from space bar to a mouse click. It works perfectly across all the platforms except Android devices mobile browsers.
In Android devices, the touch makes the user jump. If there is a long hold in the touch, the user keeps jumping even when the touch is released. This problem does not happen in iPhones or iPads or desktops.
Can I make a Javascript function where a mouse down for a certain number of seconds is cut ? Something like:
if(mousedown for 1sec)
mouseup;
Let me know if you can think of another approach.
You can use touch events rather than mouse for touch enabled devices. Refer: https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Using_Touch_Events
function is_touch_device() {
/* Function code taken from http://stackoverflow.com/a/4819886/3946520 */
return 'ontouchstart' in window // works on most browsers
|| navigator.maxTouchPoints; // works on IE10/11 and Surface
};
if(is_touch_device()) {
canvas.addEventListener('touchstart', handleTouchStart, false);
canvas.addEventListener('touchend', handleTouchEnd, false);
}
else {
// Bind Mouse Events
}
function handleTouchStart(e) {
// This code runs when user touches the canvas i.e. on touch start
}
function handleTouchEnd(e) {
// This code runs when user removes finger from canvas i.e. on touch end
}
Also note that there can be scenario where the user puts two or more fingers on the canvas. Each of them will fire 'touchstart' event. So you'll have to handle that.
You can refer to http://www.javascriptkit.com/javatutors/touchevents.shtml for a good tutorial on touch events.

Jankiness with Chrome mobile's autohiding toolbar and full-height divs

I have a div that is resized to be at the full height of the viewport on load as well as on resize. The problem that I am having is that on Google Chrome for iOS (I haven't checked Android, but I can imagine that it displays the same erratic behaviours) if I scroll down, as usual the address/tab bar scrolls up. And as it does, it fires resize events. When that happens, the div and its contents jitter and cause scrolling to be come sluggish, as well as causing other oddities with the div below.
JSFiddle: http://jsfiddle.net/mseymour/9PEC4/
I would suggest setting the height of the .home-hero only once on an smartphone, by either checking the height of the window (could cause problems, it's inconstant) or user agent sniffing.
Here are some links:
http://detectmobilebrowsers.com/
What is the best way to detect a mobile device in jQuery?
Detecting a mobile browser
Then set the height only once since on a mobile you cannot really resize the window, and you don't need to resize it on every resize event.
if (isMobile) {
fullScreenSlide(); // resize once
} else {
setResizeEvent(); // Set the event for multiple resizes
}
function setResizeEvent() {
$(window).on("resize", function () {
fullScreenSlide();
}).resize();
}
function fullScreenSlide () {
var browserheight = Math.round($(window).height());
$('.home-hero').height(browserheight);
}
You want to do as little "things" on a smartphone since smartphones are really slow performance wise compared to a desktop

How to detect mobile Safari global orientation rotation animation

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.

Is there a way to catch the zoomed-out-max event in IE10 Metro?

In Internet Explorer 10 Metro Style I want to catch the event that happens when the user zooms out to full view using pinch gesture.
I can do it either using JavaScript running in the page or a C++ code running in IE address space.
It's a hack but it's working, The extra minimum zoom is around 0.85 of the normal size so we check if the user zoomed out more than 0.87:
window.addEventListener ('resize', function () {
if (document.documentElement.clientHeight / window.innerHeight <= 0.87){
// this will run more than once while the user
// is zooming out close to the maximum level
}
}, false);
It won't work when zooming is disabled (mobile websites).

Categories