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.
The 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.--*
).If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With