I\'d like to provide separate behaviour for browsers supporting hover (e.g. desktop browsers) and ones which don\'t (e.g. touchscreen devices). Specifically I want to declar
To support Firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=1035774 ), you need to potentially write some rules twice. Note, although not specified in the question I've added pointer: coarse
on the assumption that the purpose of these rules is to target mobile screens:
/* BEGIN devices that DON'T pinch/zoom */
/* If https://bugzilla.mozilla.org/show_bug.cgi?id=1035774
is fixed then we can wrap this section in a ...
@media not all and (pointer: coarse) and (hover: none) {
.. and do away with the 'NEGATE' items below */
.myelement {
/* some styling that you want to be desktop only (style as an anchor on desktop) */
font-size: 14px;
text-decoration: underline;
border: none;
}
/* END devices that DON'T pinch/zoom */
/* BEGIN devices that DO pinch/zoom */
@media (pointer: coarse) and (hover: none) {
.myelement {
/* style as a large button on mobile */
font-size: inherit; /* maintain e.g. larger mobile font size */
text-decoration: none; /* NEGATE */
border: 1px solid grey;
}
.myelement:hover {
/* hover-only styling */
}
}
/* END devices that DO pinch/zoom */
The combination of (pointer: coarse) and (hover: none)
should become more useful to target mobile devices as mobile screens become larger and we lose the correlation between pixel dimensions and mobile vs. desktop (already the case when you wish to distinguish between tablet and desktop)
not
should prefix media type (screen, print, all, etc) and not media feature (hover, point, etc).
Wrong:
@media not (hover: none)
Correct:
@media not all and (hover: none)
Yes, its unintuitive and weird. Source (see comments).
So you do not need javascript to alternate result of media query.
From the specs:
none
Indicates that the primary pointing system can’t hover, or there is no pointing system. Examples include touchscreens and screens that use a drawing stylus.
Pointing systems that can hover, but for which doing so is inconvenient and not part of the normal way they are used, also match this value.For example, a touchscreen where a long press is treated as hovering would match hover: none.
If your browser (mobile/touch) support long-press to simulate hover, the usage of hover: none
will not work. What you can do is just use a default value and override it (with default css precedence):
body {
background: red;
}
@media (hover: hover) {
body {
background: blue;
}
}
Desktop will have blue
background and mobile/touch will have red
background
Check the following example:
https://jsfiddle.net/mcy60pvt/1/
To check the long-press option of the mobile you can use this example:
https://jsfiddle.net/mcy60pvt/3/
In the above example the green block has :hover
definition for both desktop and mobile, however for desktop the background will change to yellow
and for mobile (with long-press) it will change to white
.
Here is the css for the last example:
body {
background: red;
}
div.do-hover {
border: 1px solid black;
width: 250px;
height: 250px;
background: green;
}
div.do-hover:hover {
background: white;
}
@media (hover: hover) {
body {
background: blue;
}
div.do-hover:hover {
background: yellow;
}
}
In this example - browsers that don't support :hover
will view the hover me box with green background, and while "hover" (touch/long-press) - the background will change to white.
As for the added pseudo code:
.myelement {
/* some styling #1 */
/* note: no hover state here */
}
@media(hover: hover) {
.myelement {
/* some styling that override #1 styling in case this browser suppot the hover*/
}
.myelement:hover {
/* what to do when hover */
}
}
You can use this Sass mixin to style the hover, it will use the recommended substitute :active
for touch devices. Works on all modern browsers and IE11.
/**
Hover styling for all devices and browsers
*/
@mixin hover() {
@media (hover: none) {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
&:active {
@content;
}
}
@media (hover: hover), all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
&:hover {
@content;
}
}
}
.element {
@include hover {
background: red;
}
}
According to Artin´s answer we can address only devices that support hover with pure css, with @media not all and (hover: none)
. It looks weird but it works.
I made a Sass mixin out of this for easier use:
@mixin hover-supported {
@media not all and (hover: none) {
&:hover {
@content;
}
}
}
The following would change background-color of .container
from red to blue on hover for devices that support hover, no change for touch devices:
.container {
background-color: red;
@include hover-supported() {
background-color: blue;
}
}
Thanks to Dekel's comments I solved this by running the logic in JS and applying a class instead:
e.g.
const canHover = !(matchMedia('(hover: none)').matches);
if(canHover) {
document.body.classList.add('can-hover');
}
Then in the stylesheet:
.myElement {
background: blue;
}
.can-hover .myElement:hover {
background: red;
}
I've tested this on desktop Chrome, Safari and Firefox, and iOS Safari and it works as expected.