I currently have #media (any-hover: hover) condition in my css, and previously with (hover: hover), but neither do what I want: I would like to only enable hover event if a mobile device have some kind of hover mechanism (eg. trackpad, mouse but not limited) implanted.
However, with hover:hover at least on iPad it doesn't stimulate the hover event at all (I know hover detects if the device's primary interaction supports hover), not even I have magic keyboard connected (which has a trackpad). Then I change to any-hover and this time the iPad stimulates hover even without any accessory connected, and is interact with touch.
Hence, is there's a way to detect if a device has hover-able device connected? Should I use pointer: fine or pointer: coarse to do this? Or is there a way to do so in JS? I don't want it simply detect if there's a mouse connected, but any kind of device with hover-like mechanism.
So you want something that does not match for normal iPads and does match if someone plugs a mouse into an iPad?
pointer: fine is usually enough, but you can get even more precise with (any-hover: hover) and (pointer: fine). You really only want to enable hover content for devices that have fine-grained pointer control.
You can express this in JS as well:
let mql = window.matchMedia('(any-hover: hover) and (pointer: fine');
Then I change to any-hover and this time the iPad stimulates hover even without any accessory connected, and is interact with touch.
That seems like a bug.
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'm making a game that has 2 controls, left and right. On desktop those are the left and right cursor keys. On a machine without a keyboard or a machine primarily interacted with via touch screen I need to show buttons the user can press with their fingers.
Is there a way to detect when it's appropriate to display these touch buttons and when it's not?
solutions considered:
check for iPhone/iPad/Android
That covers most phones and tablet but of course it will be wrong on some other brands
use css media queries hover and pointer
AFAIK these won't help. A phone probably has hover: none and pointer: course but what about an iPad with a stylus. It should allow both hover:hover and pointer:fine making it indistinguishable from a desktop.
check by size
Seems flaky. Tablets have pretty large sizes. Often as large as laptops.
check for touch support
isTouchDevice = "ontouchstart" in window;
returns true on many Windows devices with touch screens
I have a simple web app with a few text inputs and the inputs toward the bottom of the page get covered up by the iPhone keyboard. This is a terrible user experience making it difficult for the user to see what text they are entering as well as selecting other elements on the page after they are done entering text.
Apple documents the behavior here: https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
I've seen several posts about this problem and there are multiple solutions for iOS app development but no real solutions for web apps.
I don't have the resources to test on multiple devices and I don't have an android device so I don't know if this problem even occurs there.
Is there a cross platform solution for detecting when a keyboard is covering the UI, how much of the UI is being covered, and an approach to ensure the input is visible?
This answer is a hack; but it's the best solution I have come up with.
Adding a padding to the bottom of the page that is large enough for the keyboard enables content to be displayed as desired when the keyboard is visible.
Using some javascript listeners and a CSS class, this padding can be added only when the keyboard is displayed.
body.keyboard {
height: calc(100% + 500px); /* add padding for keyboard */
}
The following javascript will add and remove the CSS class when an input has focus, which is the closest I can get to figuring out when the keyboard is displayed.
// focus events don't bubble, must use capture phase
document.body.addEventListener("focus", event => {
const target = event.target;
switch (target.tagName) {
case "INPUT":
case "TEXTAREA":
case "SELECT":
document.body.classList.add("keyboard");
}
}, true);
document.body.addEventListener("blur", () => {
document.body.classList.remove("keyboard");
}, true);
It's also worth noting that iOS performs an annoying zoom if the input font size is less that 16px so this is also important.
input {
font-size: 16px !important;
}
Please let me know if you have a better solution.
What about this: stackoverflow: Scroll textfield up when keyboard popsup?
Even if you're not using jquery you could still bind the focus event and scroll the page using the window.scrollTo(0, document.body.scrollHeight); function and just scroll to the bottom. Because interestingly, the space is created, but not scrolled to.
If you want to go fancy, you can check for the window size in order to determine if and how much you want to scroll.
What you can do is hide the keyboard on any click event. So, figure out when you don't want to display a keyboard and when do you really want to show it.
So once figured, you can hide the keyboard like:
InputMethodManager manager = getSystemService(INPUT_METHOD_SERVICE);
manager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);
Here's an answer that might help you: Scroll textfield up when keyboard popsup
In summary, you can make use of the visual viewport api and update the next render accordingly.
What I did is to scroll the elements into view when focused by touch.
There is no exact api or anything to know if the keyboard is open at all, or how much the keyboard covers (in chrome actually the keyboard reduces the viewport, not overlays on top), but looking at average mobiles, it covers roughly half of it.
As for the implementation, I use a kind of simple heuristic. You can go all-in if the device is a touch device see mobile device detection section here, but this doesn't necessarily cover laptops with touch screens. (and doing it based on resolution is a bad idea also)
I took this "touch device" approach one step forward, and listen on the touchstart event on form elements and set a variable something like hasTouchKeyboard, and then scroll element to top of page (or at least first half that is likely not covered) when focused if this variable is true.
The idea behind it is that the touchstart usually fires before the focused event, plus the real differentiator is that if the user touched the input field, then it is 100% that it is using a touch device AND actually used touch on it (that will likely trigger the keyboard), and not just focused it with mouse on a hybrid device.
It is definitely not bulletproof, but fairly simple and works well without any user agent magic or other crazy stuff.
For www.lokilist.com I needed a solution that worked without javascript for the text input used to enter a Session ID.
On the server, I used the "isMobileDevice()" PHP function found here:
https://stackoverflow.com/a/23874239/20094562
Then attached a css class with onfocus css only if the requesting browser was "mobile" (that function considers tablets as mobile, which is all the better for this purpose since we actually care about touch screens).
To summarize:
Detect touch screens based on user agent with isMobileDevice()
In your css, include a placeholder class like ".mobileTextInput".
Also include in your css the onfocus property of that class like ".mobileTextInput:focus". This is what will reposition your text input when selected.
If the user agent is a mobile device, add the mobileTextInput class to the text elements you want to move above the virtual keyboard.
A web page I am developing has mouse over events (both css and javascript) for a top navigation bar. It works fine when the browser window is floating, but when maximized (full screen) the hover classes have no effect anymore. It begins to act like a tablet display, I must point and click for my hover actions to take effect. I achieve the events when I click, but mouse hover changes my cursor to a text select cursor.
This does not happen on a windows computer
As a matter of fact, this behaviour is good if it is meant to support tablets but I'd appreciate if anybody could let me know if this is a bug or intended?
This is a 3+ year old unfixed bug in Chromium.
http://crbug.com/170058
I'm making a small full-screen canvas game to be played on mobile. It involves swiping around the screen with your finger. I have the canvas listening for touch events (down, move, up).
Unfortunately, the browser tries to interpret swipes left and right as browser tab navigation, and swipes up and down as hide/show URL. I don't care if the URL is hidden or not, I just want everything to stay still.
As a result of everything moving, it intermittently stops recognizing inputs to the canvas.
The way I have it set up is a canvas that takes the size of the screen, set its position to fixed at 0,0. This is to prevent scrolling (which works, except for the hide/show url thing).
If you want to see an example, here's where I'm hosting it:
http://phildogames.com/scratch/sust/index.html?game=window (you won't be able to interact on desktop because it's listening for touch events, but if you view it on mobile you should be able to open and close the windows).
tl;dr: I want to tell the browser to simply pass swipes (well, all touch events) to the dom and stop interpreting them as intents to scroll, switch tabs, hide/show URL, etc..
Thanks!
I was able to solve your problem by simply preventing touches on the body.
var myBody = document.getElementById('dabody');
myBody.addEventListener('touchstart', function(e){
e.preventDefault()
});
You can throw that in console to test. Tested on iOS Safari and Android Chrome.