Detect if a client device supports :hover and :focus states - javascript

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>
foo
<div class="sub">
<ul>
<li>mobile</li>
<li>users</li>
</ul>
</div>
</li>
<li>
bar
<div class="sub">
<ul>
<li>never</li>
<li>see me</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 and any-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

Related

Style Tweaking in JS doesn't work as planned

So, I am trying to make navbar swap colors of text and background when scrolled, with the following code it seems that background part is working just fine and it switches to white and back to gray, but when I literally changed font color in the next line, it just doesn't work. All the links are grouped in a list with the id = "nav_links". What's the deal with this? Thanks in advance!
function myFunction() {
if (document.body.scrollTop >= 0) {
console.log(document.body.scrollTop);
nav.style.backgroundColor = "white";
document.getElementById('nav_links').style.color = 'rgb(25,25,25)';
if (document.body.scrollTop === 0) {
nav.style.backgroundColor = 'rgb(25,25,25)';
document.getElementById('nav_links').style.color = "white";
}
}
}
document.addEventListener("scroll", myFunction);
<header id="nav">
<img src="logo.png" alt="logo" id="logo">
<nav>
<ul id="nav_links">
<li>Lifestyle</li>
<li>Fashion</li>
<li>Beauty</li>
<li>Health</li>
</ul>
</nav>
Anchor elements tend to have special treatment when it comes to their default styling by the browser. So we need to be quite specific when selecting them for a color change.
This snippet changes what the JS does. Instead of setting the styles directly it sets nav to have/not have a class 'scrolled' depending on whether scrollTop/pageYoffset is greater than zero or not. (Because scrollTop can't go below zero there is no need for the extra test in the script).
The presence/absence of the scrolled class causes both the background color of #nav and the color of the anchor elements in the li elements to be set in different ways.
A couple of notes on this. It has been assumed that the element with the id of nav is the one in your script which has id nav. This may not matter in practice, but if this is not true and element nav is in fact the actual nav element then you could replace #nav by #nav nav for more specificity. It's also been assumed that you don't want the bullet point suddenly appearing on the list as the background turns to white but I've indicated in the snippet how you can go back to having that if that was intended.
const nav = document.querySelector('#nav');
function myFunction() {
// if (document.body.scrollTop > 0) { //if there is some scrolling
// I ran into some problems making a Stackoverflow snippet because of the introduction of !doctype html
// so I have used this cross-browser test from #ijavid answer at https://stackoverflow.com/questions/2717252/document-body-scrolltop-is-always-0-in-ie-even-when-scrolling
if (typeof window.pageYOffset != 'undefined' ? window.pageYOffset : document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ? document.body.scrollTop : 0) {
nav.classList.add('scrolled');
} else { // there isn't any scrolling
nav.classList.remove('scrolled');
}
}
document.addEventListener("scroll", myFunction);
body {
height: 200vh;
background-color: blue;
}
#nav {
background-color: rgb(25, 25, 25);
}
#nav.scrolled {
background-color: white;
}
#nav #nav_links li>a {
color: white;
}
#nav.scrolled #nav_links li>a {
color: rgb(25, 25, 25);
}
/* if you want the bullet points to show when the background goes white remove the next two settings */
#nav_links>li {
list-style: none;
}
/* this is put in because of a bug in Safari which would mar the accessibility of the list */
/* (Safari would ignore it) see https://developer.mozilla.org/en-US/docs/Web/CSS/list-style */
#nav_links>li::before {
content: "\200B";
}
</style></head><body><!doctype html><html><head><style>body {
height: 200vh;
background-color: blue;
}
#nav {
background-color: rgb(25, 25, 25);
}
#nav.scrolled {
background-color: white;
}
#nav #nav_links li>a {
color: white;
}
#nav.scrolled #nav_links li>a {
color: rgb(25, 25, 25);
}
/* if you want the bullet points to show when the background goes white remove the next two settings */
#nav_links>li {
list-style: none;
}
/* this is put in because of a bug in Safari which would mar the accessibility of the list */
/* (Safari would ignore it) see https://developer.mozilla.org/en-US/docs/Web/CSS/list-style */
#nav_links>li::before {
content: "\200B";
}
<header id="nav">
<img src="logo.png" alt="logo" id="logo">
<nav>
<ul id="nav_links">
<li>Lifestyle</li>
<li>Fashion</li>
<li>Beauty</li>
<li>Health</li>
</ul>
</nav>
</header>

What is an HTML/JavaScript equivalent of this user sortable list that works on mobile?

In my Java desktop application I have a list of items, and the user can change their order by selecting items and using the Up/Down buttons to change their position in the list.
What would be an equivalent way to do this in HTML? A <select> would not work because on mobile devices, selects only ever show one row regardless of what the size is set to.
Using JavaScript, you can quite easily implement such a control yourself. Here is a very quick and dirty example that works in modern browsers (most notably, not in Internet Explorer). Since mobile browsers do fire click events when handlers are attached directly to elements, this also works on mobile/touch devices. Of course, you may want to implement a slightly different layout for mobile devices, to make it easier to click elements. It may also be more intuitive to implement drag-and-dropping of elements to sort them on touch devices.
To make this work in a regular old form, a hidden input can be added and updated as well. To this end, we can add data attributes on the options, since they can be read easily with JavaScript. Another option would be to intercept the submit event of the form and doing the submit manually via AJAX, loading the event order then. The below demo implements the first option: populating a hidden input.
function orderInput(list, upButton, downButton, input) {
updateInput(list, input);
// enable selection of list elements
for (let li of list.querySelectorAll('li')) {
li.addEventListener('click', () => {
for (let sibling of li.parentNode.children) {
if (!sibling.isSameNode(li)) {
sibling.classList.remove('selected');
}
}
li.classList.toggle('selected');
});
}
// enable moving an element up
upButton.addEventListener('click', () => {
var li = list.querySelector('li.selected');
if (li.previousElementSibling !== null) {
li.parentNode.insertBefore(li, li.previousElementSibling);
updateInput(list, input);
}
});
// enable moving an element down
downButton.addEventListener('click', () => {
var li = list.querySelector('li.selected');
if (li.nextElementSibling !== null) {
li.parentNode.insertBefore(li, li.nextElementSibling.nextElementSibling);
updateInput(list, input);
}
});
}
function updateInput(list, input) {
var values = [];
for (let li of list.querySelectorAll('li')) {
values.push(li.dataset.value);
}
input.value = values.join(';');
}
// instantiate on our fruits
orderInput(
document.querySelector('ul'),
document.getElementById('up'),
document.getElementById('down'),
document.querySelector('input[name="fruits"]')
);
ul {
padding-left: 0;
list-style-type: none;
border: 1px solid darkgray;
}
li {
padding: 2px 4px;
}
li:nth-child(even) {
background-color: lightgray;
}
li.selected {
background-color: blue;
color: white;
}
<p>Click an item in the list to select it, change its place in the list using the up and down buttons.</p>
<button id="up">Up</button> - <button id="down">Down</button>
<ul>
<li data-value="red-apple">Apple</li>
<li data-value="yellow-banana">Banana</li>
<li data-value="cherries">Cherry</li>
<li data-value="d-fruit">Dragon Fruit</li>
<li data-value="old-berry">Elderberry</li>
<li data-value="not-a-figure">Fig</li>
<li data-value="blue-grape">Grape</li>
</ul>
<input type="hidden" name="fruits" />
For the requirements that you want, try this..
In the function selected() you can write code as to what shpuld happen if an element is selected.
function selected(str){
console.log(str+" is selected.");
}
ul {
position: absolute;
top: 0;
width: 200px;
margin: 0;
padding: 0;
list-style: none;
}
ul a:hover {
color: red;
background: none;
}
li a {
display: block;
text-decoration: none;
}
li a:hover {
text-decoration: none;
color: red;
background: rgba(255, 255, 255, 0.2);
}
li a:focus {
text-decoration: none;
}
<ul style="height:4em; overflow-y:auto;">
<li><a onclick="selected('1')">One</a></li>
<li><a onclick="selected('2')">Two</a></li>
<li><a onclick="selected('3')">Three</a></li>
<li><a onclick="selected('4')">Four</a></li>
<li><a onclick="selected('5')">Five</a></li>
</ul>
I recommend the use of JQuery plugin or small Javascript library like Sortable, that supports change the order using drag and even touch in portable devices.
Example: http://rubaxa.github.io/Sortable/

CSS li dropdown on touch devices

EDIT: Thanks for the answers. Do these suggestions keep the li:hover dropdown working, just adding the onclick feature for touch devices? I don't want desktop users to have to click, the menu should appear on:hover for them.
I have done my research on this subject but can't seem to find a good solution.
My site http://www.eastbournenl.com
Menu's in question are 'League Info' and 'Results' in the top navigation bar.
My CSS dropdown menu activates on li:hover (the li item is NOT a link, I changed the cursor so it appears as a hand but clicking will take you nowhere).
However, clearly this doesn't work on touch devices.
Is there a way to maintain the setup I have currently with the li:hover working on non-touch devices, but adding some javascript to enable functionality for touchscreen devices?
HTML
<div id="link_bar">
<ul>
<li>League Info
<ul class="drop">
<li>Team Directory</li>
<li>Fixtures</li>
<li>Rules</li>
<li>Umpire Directory</li>
</ul>
</li>
</ul>
</div>
And CSS
#link_bar ul ul {
display: none
}
#link_bar ul li:hover > ul {
display: block
}
#link_bar ul:after {
content: "";
clear:both;
display:block
}
#link_bar ul ul {
position: absolute;
top: 1em;
width: 10em;
padding-top: 1em;
margin-left: 0;
}
#link_bar ul ul li {float:none ; position: relative ; padding: 1em 1em 1em 0}
#link_bar ul ul li {width:100%}
#link_bar .drop li:hover a {color: #99FF33}
With Modernizr, you can target touch devices, so include it in your code, and then :
JS part :
if(Modernizr.touch){
$('.hasDropDown').click(function(){
$(this).find('.drop').addClass('visible');
});
}
HTML part :
<div id="link_bar">
<ul>
<li class="hasDropDown">League Info
<ul class="drop">
<li>Team Directory</li>
<li>Fixtures</li>
<li>Rules</li>
<li>Umpire Directory</li>
</ul>
</li>
</ul>
</div>
CSS :
.visible {
display: block;
}
That's weird, most of touch devices emulate hover events.
as you know touch device not supported hover effects
you can use some plugin for that,like Modernizer.js
another solution i see on the Web is:
div#menu ul li:hover ul needs to become div#menu ul li:active ul for it to respond on touch devices because they don't support hover states.
my specific solution for you is:
Jquery Smart Menu

jQuery Toggle does not show on window resize

I'm using jQuery slideToggle function and the media queries.
The problem is that when I resize the window to small size, the toggle links appear. Now if I click on toggle, it works fine, but when I resize the window back to large size, the hidden element still do not appear.
If I do not click on the toggle link, and resize the window back to large size, it works fine.
Demo to see the problem:
Please check the demo here, where you can see the problem:
http://jsfiddle.net/3Jj7J/
Resize the window to small size that you see "Main Menu" link. When you click on it, you will see the toggle. Now if you resize it back to large size, the normal links will still not appear.
Here's my code:
HTML:
<div class="bar">
<a class="toggle" href="#">MAIN MENU</a>
</div>
<div class="nav">
<div class="wrap">
<ul>
<li>Link 1</li>
<li>Link 2</li>
</ul>
</div>
</div>
CSS:
.bar{
display: none;
border: 1px solid red;
}
#media screen and (max-width: 500px) {
.nav ul {
display: none;
}
.bar{
display: block;
}
}
jQuery:
var menu = jQuery('.nav .wrap > ul');
jQuery(".toggle").click(function() {
menu.slideToggle(500);
});
add on window resize event handler :
var menu = jQuery('.nav .wrap > ul');
jQuery(".toggle").click(function() {
menu.slideToggle(500);
});
jQuery(window).on('resize', function(){
if(!jQuery(".toggle").is(":visible") && !menu.is(':visible'))
{
menu.show();
}
});
jsFiddle : http://jsfiddle.net/3Jj7J/1/
update: this is alternative solution (just remove inline display property, so it will use css rule).
jQuery(window).on('resize', function(){
if(!jQuery(".toggle").is(":visible") && !menu.is(':visible'))
{
menu.css({'display':''});
}
});
DEMO
I've read your problem and tested it myself. Now, I've made the Link appear by doing the following:
CSS:
#bar {
display: none;
color: #000000;
border: 1px solid red;
}
#media screen and (max-width: 500px) {
#nav ul {
display: none;
}
.bar{
display: block;
}
}
To see for yourself (http://jsfiddle.net/hSZ7t/) What I've done is changed your CSS. Instead of you using:
.bar {
It's now:
#bar {

Move selection nipple to selected div ID on link click

I am working on a website that features many links on the same page:
http://www.alexanderlozada.com
To let the user know what item they are currently viewing, I'd like to implement a small triangle that points at the currently selected item.
example:
How could I go about doing this without making each link a separate page?
sample of the link I am working with- (I have to keep the current href and rel)
<a class="grey show_hide" href="#" rel="#projects" >
PROJECTS
</a>
In most cases this is done by using pseudo elements :before and/or :after like so (read full article)
CSS:
/* creates triangle */
.selected:after {
content:"";
display:block; /* reduce the damage in FF3.0 */
position:absolute;
bottom:-2px;
left:50%;
width:0;
margin-left:-10px;
border-width:0px 15px 15px;
border-style:solid;
border-color:white transparent;
}
div.links {
display: inline-block;
position:relative; // you must have this to position the triangle propery
width: 25%;
height: 45px; // adjust height to fit the menu
float: left;
text-align: center;
font-size: 24px;
padding-top: 10px;
}
jQuery:
$(function(){
$('.show_hide').click(function(){
$('div.links').removeClass('selected'); // remove all other 'selected' links
$(this).parent().addClass('selected'); // sets the current .links to be selected
});
});
Add an active class in your :
<a class="btn active">menu link</a>
css:
.btn.active { background:url(cursor-active.png) bottom center no-repeat; }
js:
$('.btn').click(function(){
$('.btn').removeClass('active');
$(this).addClass('active');
});
You can see here : FIDDLE

Categories

Resources