Sounds like a simple problem, but turns out to be quite challenging to solve. For some website I have contents that are only to be shown if a user hovers/focuses a link. The link however has a target itself.
If one of those links is clicked by a touch screen user the browser instantly goes to the href
location. This means the hover contents are never visible!
This is why users which do not have a mouse (or another device to hover like a magic remote control) should see alternative content. But how can I detect this?
$(document).on('click','#my-menu-inner > ul > li > a',function(e) {
if(clientHasInputDeviceSupportingHover()) {
return true;
} else {
e.preventDefault();
$('#for-no-hover-visitors').html('');
$(this).clone().appendTo('#for-no-hover-visitors');
$(this).next().clone().appendTo('#for-no-hover-visitors');
}
});
function clientHasInputDeviceSupportingHover() {
// HOW CAN I DETECT THIS???
if($('#checkhover:checked').length > 0) {
return true;
}
return false;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
#my-menu-inner > ul {
margin:10px;
width:100%;
background-color:yellow;
list-style-type:none;
position:relative;
}
#my-menu-inner > ul > li {
float:left;
margin:20px;
}
#my-menu-inner > ul > li > a {
padding:20px;
border:1px solid black;
display:block;
}
#my-menu-inner > ul > li > div.sub {
position:absolute;
top:calc(100% - 20px);
background-color:red;
padding:40px;
display:none;
left:0;
width:100vw;
}
#my-menu-inner > ul > li a:hover + div.sub, #my-menu-inner > ul > li a:focus + div.sub,
#my-menu-inner > ul > li > div.sub:hover, #my-menu-inner > ul > li > div.sub:focus {
display:block;
}
<script src=".1.1/jquery.min.js"></script>
Simulate for Client supporting hover: <input type="checkbox" id="checkhover" />
<div id="my-menu">
<div id="my-menu-inner">
<ul class="clearfix">
<li>
<a href="/">foo</a>
<div class="sub">
<ul>
<li><a href="/">mobile</a></li>
<li><a href="/">users</a></li>
</ul>
</div>
</li>
<li>
<a href="/">bar</a>
<div class="sub">
<ul>
<li><a href="/">never</a></li>
<li><a href="/">see me</a></li>
</ul>
</div>
</li>
</ul>
</div>
</div>
<div id="for-no-hover-visitors"></div>
Sounds like a simple problem, but turns out to be quite challenging to solve. For some website I have contents that are only to be shown if a user hovers/focuses a link. The link however has a target itself.
If one of those links is clicked by a touch screen user the browser instantly goes to the href
location. This means the hover contents are never visible!
This is why users which do not have a mouse (or another device to hover like a magic remote control) should see alternative content. But how can I detect this?
$(document).on('click','#my-menu-inner > ul > li > a',function(e) {
if(clientHasInputDeviceSupportingHover()) {
return true;
} else {
e.preventDefault();
$('#for-no-hover-visitors').html('');
$(this).clone().appendTo('#for-no-hover-visitors');
$(this).next().clone().appendTo('#for-no-hover-visitors');
}
});
function clientHasInputDeviceSupportingHover() {
// HOW CAN I DETECT THIS???
if($('#checkhover:checked').length > 0) {
return true;
}
return false;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
#my-menu-inner > ul {
margin:10px;
width:100%;
background-color:yellow;
list-style-type:none;
position:relative;
}
#my-menu-inner > ul > li {
float:left;
margin:20px;
}
#my-menu-inner > ul > li > a {
padding:20px;
border:1px solid black;
display:block;
}
#my-menu-inner > ul > li > div.sub {
position:absolute;
top:calc(100% - 20px);
background-color:red;
padding:40px;
display:none;
left:0;
width:100vw;
}
#my-menu-inner > ul > li a:hover + div.sub, #my-menu-inner > ul > li a:focus + div.sub,
#my-menu-inner > ul > li > div.sub:hover, #my-menu-inner > ul > li > div.sub:focus {
display:block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Simulate for Client supporting hover: <input type="checkbox" id="checkhover" />
<div id="my-menu">
<div id="my-menu-inner">
<ul class="clearfix">
<li>
<a href="http://www.example.com/foo/">foo</a>
<div class="sub">
<ul>
<li><a href="http://www.example.com/mobile/">mobile</a></li>
<li><a href="http://www.example.com/users/">users</a></li>
</ul>
</div>
</li>
<li>
<a href="http://www.example.com/bar/">bar</a>
<div class="sub">
<ul>
<li><a href="http://www.example.com/never/">never</a></li>
<li><a href="http://www.example.com/see-me/">see me</a></li>
</ul>
</div>
</li>
</ul>
</div>
</div>
<div id="for-no-hover-visitors"></div>
The problem is clientHasInputDeviceSupportingHover()
. What is the most reliable way to find this out?
What we know so far
It is possible to detect a touch device: What's the best way to detect a 'touch screen' device using JavaScript?
Mouse detection at least might work"onclick": How to detect if a device has mouse support?
In general there are a lot of different possible input devices: https://en.wikipedia.org/wiki/Input_device#Pointing_device
A generic / more reliable solution would be very welcome.
Share Improve this question edited Oct 10, 2018 at 16:14 Blackbam asked Oct 10, 2018 at 14:24 BlackbamBlackbam 19.4k30 gold badges110 silver badges165 bronze badges 6- 1 You can check if the device is a mobile. Plenty of example on google how to check if its touch device. – Constantin Chirila Commented Oct 10, 2018 at 14:27
- @ConstantinChirila This is not useful. What about a 4K touchscreen for instance? What about a mini-netbook with a touchpad? – Blackbam Commented Oct 10, 2018 at 14:30
- 1 this has been asked before - eg here: stackoverflow.com/questions/21054126/… Unfortunately it seems there is no good solution though :( (I had exactly this same question recently, and ended up just testing for touch support - which is not ideal because users with access to both get the worse experience, but I didn't see anything better.) – Robin Zigmond Commented Oct 10, 2018 at 14:35
- @RobinZigmond The question for mouse support is similar as there are only very few input possibilities except for a mouse which support hover / focus I guess. But I hope there might be a script which could simulate a hover or something like this and as a result give an adequate answer? – Blackbam Commented Oct 10, 2018 at 14:49
- You can check touch support on any type of device. Probably my comment threw you off because i said Mobile. But as @Robin Zigmond said, users with both inputs get the worse experience. – Constantin Chirila Commented Oct 10, 2018 at 15:07
3 Answers
Reset to default 18The W3C seems to have recognized this problem and has introduced the hover feature:
The hover media feature is used to query the user’s ability to
hover
over elements on the page with the primary pointing device. If a device has multiple pointing devices, the hover media feature must reflect the characteristics of the “primary” pointing device, as determined by the user agent. (To query the capabilities of any available pointing devices, see the any-hover media feature.)
There is even a media query to check if there is any possibility to hover:
The
any-pointer
andany-hover
media features are identical to the pointer and hover media features, but they correspond to the union of capabilities of all the pointing devices available to the user. In the case of any-pointer, more than one of the values can match, if different pointing devices have different characteristics.
Code samples:
/* Primary input mechanism system can
hover over elements with ease */
@media (hover: hover) { ... }
/* Primary input mechanism cannot hover
at all or cannot conveniently hover
(e.g., many mobile devices emulate hovering
when the user performs an inconvenient long tap),
or there is no primary pointing input mechanism */
@media (hover: none) { ... }
/* One or more available input mechanism(s)
can hover over elements with ease */
@media (any-hover: hover) { ... }
/* One or more available input mechanism(s) cannot
hover (or there are no pointing input mechanisms) */
@media (any-hover: none) { ... }
Official draft: https://drafts.csswg.org/mediaqueries/#hover
This feature is still at risk, but I really hope it will be fully supported soon as it is already widely supported: https://caniuse.com/#feat=css-media-interaction
Further read: https://css-tricks.com/touch-devices-not-judged-size/
For Chrome test your device here: https://googlechrome.github.io/samples/media-hover-pointer/
Test with JavaScript: https://jsfiddle.net/Blackbam/zkd2cs0t/16/
The best solution for now is most probably to use those media queries with a fallback solution using touch detection via document.createEvent("TouchEvent");
and mouse detection via mousemove.hasMouse
.
One approach, if you're able to use up-to-date CSS (and check the compatibility table in the linked resources):
/* Here we use media queries to test the device's
support for the 'pointer' property, here we
check if the 'fine' property-value is supported
by the device: */
@media (pointer:fine) {
/* Here we set variables to use in styling
the subsequent content which will identify
the pointer support: */
:root {
--hasTouch: orangered;
}
}
@media (pointer:coarse) {
:root {
--hasTouch: limegreen;
}
}
@media (pointer:fine) and (pointer:coarse) {
:root {
--hasTouch: skyblue;
}
}
/* Note that we don't specify the variable
in the event of the device not supporting
the 'pointer' property or property-value;
therefore if the <div> is white (#fff) then
we're using the default value from the
var() function: */
div {
background-color: var(--hasTouch, #fff);
height: 5em;
border: 2px solid #000;
}
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
@media (pointer:fine) {
:root {
--hasTouch: orangered;
}
}
@media (pointer:coarse) {
:root {
--hasTouch: limegreen;
}
}
@media (pointer:fine) and (pointer:coarse) {
:root {
--hasTouch: skyblue;
}
}
div {
background-color: var(--hasTouch, #fff);
height: 5em;
border: 2px solid #000;
}
ul {
width: 80vw;
margin: 0.5em auto;
list-style-type: none;
}
li::before {
content: '';
display: inline-block;
width: 1em;
height: 1em;
background-color: currentColor;
border-radius: 50%;
border: 2px solid #000;
}
.failure {
color: #000;
}
.orangered::before {
color: orangered;
}
.limegreen::before {
color: limegreen;
}
.skyblue::before {
color: skyblue;
}
<div></div>
<ul>
<li class="failure"> - Doesn't appear to understand <code>pointer</code>.</li>
<li class="orangered"> - Probably uses a mouse.</li>
<li class="limegreen"> - Probably uses touch</li>
<li class="skyblue"> - Could maybe have both mouse <em>and</em> touch.</li>
</ul>
JS Fiddle demo.
References:
@media
.@media (pointer)
property.- CSS custom properties (
--*
).
Most designers seem to have the opposite problem, ie getting rid of annoying double taps to get to an element.
However you might try the below to solve the problem and save a lot of extra code or device detection;
$("*").on("touchend", function(e) { $(this).hover(); });
notes
- you can replace * with specific classes or types
- you can add events to touchend, like click or mouseover
- only tested on iphone XR ois 12.1 and ipad ios 9.5, safari & chrome