I want to ignore all :hover CSS declarations if a user visits our website via touch device. Because the :hover CSS does not make sense, and it can even be disturbing if a tablet triggers it on click/tap because then it might stick until the element loses focus. To be honest, I don't know why touch devices feel the need to trigger :hover in first place - but this is reality, so this problem is reality as well.
a:hover {
color:blue;
border-color:green;
/* etc. > ignore all at once for touch devices */
}
So, (how) can I remove/ignore all CSS :hover declarations at once (without having to know each one) for touch devices after having them declared?
tl;dr use this: https://jsfiddle.net/57tmy8j3/
If you're interested why or what other options there are, read on.
Quick'n'dirty - remove :hover styles using JS
You can remove all the CSS rules containing :hover using Javascript. This has the advantage of not having to touch CSS and being compatible even with older browsers.
function hasTouch() {
return 'ontouchstart' in document.documentElement
|| navigator.maxTouchPoints > 0
|| navigator.msMaxTouchPoints > 0;
}
if (hasTouch()) { // remove all the :hover stylesheets
try { // prevent exception on browsers not supporting DOM styleSheets properly
for (var si in document.styleSheets) {
var styleSheet = document.styleSheets[si];
if (!styleSheet.rules) continue;
for (var ri = styleSheet.rules.length - 1; ri >= 0; ri--) {
if (!styleSheet.rules[ri].selectorText) continue;
if (styleSheet.rules[ri].selectorText.match(':hover')) {
styleSheet.deleteRule(ri);
}
}
}
} catch (ex) {}
}
Limitations: stylesheets must be hosted on the same domain (that means no CDNs). Disables hovers on mixed mouse & touch devices like Surface or iPad Pro, which hurts the UX.
CSS-only - use media queries
Place all your :hover rules in a #media block:
#media (hover: hover) {
a:hover { color: blue; }
}
or alternatively, override all your hover rules (compatible with older browsers):
a:hover { color: blue; }
#media (hover: none) {
a:hover { color: inherit; }
}
Limitations: works only on iOS 9.0+, Chrome for Android or Android 5.0+ when using WebView. hover: hover breaks hover effects on older browsers, hover: none needs overriding all the previously defined CSS rules. Both are incompatible with mixed mouse & touch devices.
The most robust - detect touch via JS and prepend CSS :hover rules
This method needs prepending all the hover rules with body.hasHover. (or a class name of your choice)
body.hasHover a:hover { color: blue; }
The hasHover class may be added using hasTouch() from the first example:
if (!hasTouch()) document.body.className += ' hasHover'
However, this whould have the same drawbacks with mixed touch devices as previous examples, which brings us to the ultimate solution. Enable hover effects whenever a mouse cursor is moved, disable hover effects whenever a touch is detected.
function watchForHover() {
// lastTouchTime is used for ignoring emulated mousemove events
let lastTouchTime = 0
function enableHover() {
if (new Date() - lastTouchTime < 500) return
document.body.classList.add('hasHover')
}
function disableHover() {
document.body.classList.remove('hasHover')
}
function updateLastTouchTime() {
lastTouchTime = new Date()
}
document.addEventListener('touchstart', updateLastTouchTime, true)
document.addEventListener('touchstart', disableHover, true)
document.addEventListener('mousemove', enableHover, true)
enableHover()
}
watchForHover()
This should work basically in any browser and enables/disables hover styles as needed.
Here's the full example - modern: https://jsfiddle.net/57tmy8j3/
Legacy (for use with old browsers): https://jsfiddle.net/dkz17jc5/19/
2020 Solution - CSS only - No Javascript
Use media hover with media pointer will help you resolve this issue. Tested on chrome Web and android mobile. I known this old question but I didn't find any solution like this.
#media (hover: hover) and (pointer: fine) {
a:hover { color: red; }
}
<a href="#" >Some Link</a>
hover CSS Media Feature to the rescue! Using only CSS, you can override styles when a device does not have hover capabilities.
The following demo is supported by modern touch devices.
/* hover query styles */
a {
color: red;
font-size: 3em;
}
a:hover {
color: blue;
}
#media (hover: none) {
a:link,
a:visited {
color: blue;
text-decoration: none;
border: 0.1em solid currentColor;
padding: 0 0.1em;
}
}
/* used to show if demo browser has hover capabilities */
.detection:before {
content: 'UNKNOWN';
color: red;
}
#media(hover) {
.detection:before {
content: 'YES';
color: green;
}
}
#media (hover: none) {
.detection:before {
content: 'NO';
}
}
<p>Hoverable pointer detected: <span class="detection"></span></p>
<h3>Experiences between device hover capabilities</h3>
<p>If the device has a hover capability, the link below:</p>
<ul>
<li>should be red</li>
<li>should be blue when hovered</li>
</ul>
<p>If the device does not have a hover capability, the link below:</p>
<ul>
<li>should always be blue</li>
<li>should be surrounded by a blue border</li>
</ul>
<p>Link</p>
Note: Keep in mind that since a Surface PC's primary input (capability) is a mouse, it will end up being a blue link, even if it's a detached (tablet) screen. Browsers will (should) always default to the most precise input's capability.
According to Jason´s answer we can address only devices that doesn't support hover with pure css media queries. We can also address only devices that support hover, like moogal´s answer in a similar question, 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;
}
}
}
Update 2019-05-15: I recommend this article from Medium that goes through all different devices that we can target with CSS. Basically it's a mix of these media rules, combine them for specific targets:
#media (hover: hover) {
/* Device that can hover (desktops) */
}
#media (hover: none) {
/* Device that can not hover with ease */
}
#media (pointer: coarse) {
/* Device with limited pointing accuracy (touch) */
}
#media (pointer: fine) {
/* Device with accurate pointing (desktop, stylus-based) */
}
#media (pointer: none) {
/* Device with no pointing */
}
Example for specific targets:
#media (hover: none) and (pointer: coarse) {
/* Smartphones and touchscreens */
}
#media (hover: hover) and (pointer: fine) {
/* Desktops with mouse */
}
I love mixins, this is how I use my hover mixin to only target devices that supports it:
#mixin on-hover {
#media (hover: hover) and (pointer: fine) {
&:hover {
#content;
}
}
}
button {
#include on-hover {
color: blue;
}
}
I have encountered the same problem (in my case with Samsung mobile browsers) and therefore I stumbled upon this question.
Thanks to Calsal's answer I found something that I believe will exclude virtually all desktop browsers because it seems to be recognized by the mobile browsers I tried (see screenshot from a compiled table: CSS pointer feature detection table ).
MDN web docs state that
The pointer CSS #media feature can be used to apply styles based on
whether the user's primary input mechanism is a pointing device, and
if so, how accurate it is
.
What I discovered is that pointer: coarse is something that is unknown to all desktop browsers in the attached table but known to all mobile browsers in the same table. This seems to be most effective choice because all other pointer keyword values give inconsistent results.
Hence you could construct a media query like Calsal described but slightly modified. It makes use of a reversed logic to rule out all touch devices.
Sass mixin:
#mixin hover-supported {
/*
* https://developer.mozilla.org/en-US/docs/Web/CSS/#media/pointer
* coarse: The primary input mechanism includes a pointing device of limited accuracy.
*/
#media not all and (pointer: coarse) {
&:hover {
#content;
}
}
}
a {
color:green;
border-color:blue;
#include hover-supported() {
color:blue;
border-color:green;
}
}
Compiled CSS:
a {
color: green;
border-color: blue;
}
#media not all and (pointer: coarse) {
a:hover {
color: blue;
border-color: green;
}
}
It is also described in this gist I created after researching the problem.
Codepen for empirical research.
UPDATE (2018):
As of writing this update, 2018-08-23, and pointed out by #DmitriPavlutin this technique no longer seems to work with Firefox desktop.
UPDATE (2021):
It has been pointed out to me that it seems to work as of Firefox 87.
I'm dealing with a similar problem currently.
There are two main options that occur to me immediately: (1) user-string checking, or (2) maintaining separate mobile pages using a different URL and having users choose what's better for them.
If you're able to use an internet duct-tape language such as PHP or Ruby, you can check the user string of the device requesting a page, and simply serve the same content but with a <link rel="mobile.css" /> instead of the normal style.
User strings have identifying information about browser, renderer, operating system, etc. It would be up to you to decide what devices are "touch" versus non-touch. You may be able to find this information available somewhere and map it into your system.
A. If you're allowed to ignore old browsers, you just have to add a single rule to the normal, non-mobile css, namely: EDIT: Erk. After doing some experimentation, I discovered the below rule also disables the ability to follow links in webkit-browsers in addition to just causing aesthetic effects to be disabled - see http://jsfiddle.net/3nkcdeao/ As such, you'll have to be a bit more selective as to how you modify rules for the mobile case than what I show here, but it may be a helpful starting point:
* {
pointer-events: none !important; /* only use !important if you have to */
}
As a sidenote, disabling pointer-events on a parent and then explicitly enabling them on a child currently causes any hover-effects on the parent to become active again if a child-element enters :hover.
See http://jsfiddle.net/38Lookhp/5/
B. If you're supporting legacy web-renderers, you'll have to do a bit more work along the lines of removing any rules which set special styles during :hover. To save everyone time, you might just want to build an automated copying + seding command which you run on your standard style sheets to create the mobile versions. That would allow you to just write/update the standard code and scrub away any style-rules which use :hover for the mobile version of your pages.
(I) Alternatively, simply make your users aware that you have an m.website.com for mobile devices in addition to your website.com. Though subdomaining is the most common way, you could also have some other predictable modification of a given URL to allow mobile users to access the modified pages. At that stage, you would want to be sure they don't have to modify the URL every time they navigate to another part of the site.
Again here, you may be able to just add an extra rule or two to the stylesheets or be forced to do something slightly more complicated using sed or a similar utility. It would probably be easiest to apply :not to your styling rules like div:not(.disruptive):hover {... wherein you would add class="disruptive" to elements doing annoying things for mobile users using js or the server language, instead of munging the CSS.
(II) You can actually combine the first two and (if you suspect a user has wandered to the wrong version of a page) you can suggest that they switch into/out of the mobile-type display, or simply have a link somewhere which allows users to flop back and forth. As already-stated, #media queries might also be something to look use in determining what's being used to visit.
(III) If you're up for a jQuery solution once you know what devices are "touch" and which aren't, you might find CSS hover not being ignored on touch-screen devices helpful.
try this:
#media (hover:<s>on-demand</s>) {
button:hover {
background-color: #color-when-NOT-touch-device;
}
}
UPDATE: unfortunately W3C has removed this property from the specs (https://github.com/w3c/csswg-drafts/commit/2078b46218f7462735bb0b5107c9a3e84fb4c4b1).
You can use Modernizr JS (see also this StackOverflow answer), or make a custom JS function:
function is_touch_device() {
return 'ontouchstart' in window // works on most browsers
|| navigator.maxTouchPoints; // works on IE10/11 and Surface
};
if ( is_touch_device() ) {
$('html').addClass('touch');
} else {
$('html').addClass('no-touch');
}
to detect the support of touch event in the browser, and then assign a regular CSS property, traversing the element with the html.no-touch class, like this:
html.touch a {
width: 480px;
}
/* FOR THE DESKTOP, SET THE HOVER STATE */
html.no-touch a:hover {
width: auto;
color:blue;
border-color:green;
}
To make the current answer also work in IE11 (if you're still supporting that though):
#media (hover: hover) and (pointer: fine), only screen and (-ms-high-contrast:active), (-ms-high-contrast:none) {
a:hover { color: red; }
}
<a href="#" >Some Link</a>
It was helpful for me: link
function hoverTouchUnstick() {
// Check if the device supports touch events
if('ontouchstart' in document.documentElement) {
// Loop through each stylesheet
for(var sheetI = document.styleSheets.length - 1; sheetI >= 0; sheetI--) {
var sheet = document.styleSheets[sheetI];
// Verify if cssRules exists in sheet
if(sheet.cssRules) {
// Loop through each rule in sheet
for(var ruleI = sheet.cssRules.length - 1; ruleI >= 0; ruleI--) {
var rule = sheet.cssRules[ruleI];
// Verify rule has selector text
if(rule.selectorText) {
// Replace hover psuedo-class with active psuedo-class
rule.selectorText = rule.selectorText.replace(":hover", ":active");
}
}
}
}
}
}
This is also a possible workaround, but you will have to go through your css and add a .no-touch class before your hover styles.
Javascript:
if (!("ontouchstart" in document.documentElement)) {
document.documentElement.className += " no-touch";
}
CSS Example:
<style>
p span {
display: none;
}
.no-touch p:hover span {
display: inline;
}
</style>
<p>Tap me<span>You tapped!</span></p>
Source
P.s. But we should remember, there are coming more and more touch-devices to the market, which are also supporting mouse input at the same time.
After going through the previous answers, this worked for me.
This hover will only work for desktops which has hover. And remove if you have any other hover codes for this class/id
#media (hover: hover) {
.toggle-label:hover {
background-color: #69c9ff;
color: #ffffff;
}
}
This might not be a perfect solution yet (and it’s with jQuery) but maybe it’s a direction / concept to work on: what about doing it the other way round? Which means deactivating the :hover css states by default and activate them if a mousemove event is detected anywhere on the document. Of course this does not work if someone deactivated js. What else might speak against doing it this way round?
Maybe like this:
CSS:
/* will only work if html has class "mousedetected" */
html.mousedetected a:hover {
color:blue;
border-color:green;
}
jQuery:
/* adds "mousedetected" class to html element if mouse moves (which should never happen on touch-only devices shouldn’t it?) */
$("body").mousemove( function() {
$("html").addClass("mousedetected");
});
Try this (i use background and background-color in this example):
var ClickEventType = ((document.ontouchstart !== null) ? 'click' : 'touchstart');
if (ClickEventType == 'touchstart') {
$('a').each(function() { // save original..
var back_color = $(this).css('background-color');
var background = $(this).css('background');
$(this).attr('data-back_color', back_color);
$(this).attr('data-background', background);
});
$('a').on('touchend', function(e) { // overwrite with original style..
var background = $(this).attr('data-background');
var back_color = $(this).attr('data-back_color');
if (back_color != undefined) {
$(this).css({'background-color': back_color});
}
if (background != undefined) {
$(this).css({'background': background});
}
}).on('touchstart', function(e) { // clear added stlye="" elements..
$(this).css({'background': '', 'background-color': ''});
});
}
css:
a {
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
The dirty way... Not elegant, but the easiest way that can save you.
Remove anything that characterizes the hover.
.your-class:hover:before {
color: blue;
background: linear-gradient(to bottom, rgba(231,56,39,0) 0%, #aaaaaa 100%);
}
#media all and (min-width:320px) and (max-width: 960px) {
.your-class:hover:before {
color: black;
background: transparent;
}
}
If Your issue is when you touch/tap on android and whole div covered by blue transparent color! Then you need to just change the
CURSOR : POINTER;
to
CURSOR : DEFAULT;
use mediaQuery to hide in mobile phone/Tablet.
This works for me.
Use media hover with media pointer will help you resolve this issue.
#media (hover: none) and (pointer: coarse) {
/* Smartphones and touchscreens */
}
Try this easy 2019 jquery solution, although its been around a while;
add this plugin to head:
src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"
add this to js:
$("*").on("touchend", function(e) { $(this).focus(); }); //applies to all elements
some suggested variations to this are:
$(":input, :checkbox,").on("touchend", function(e) {(this).focus);}); //specify elements
$("*").on("click, touchend", function(e) { $(this).focus(); }); //include click event
css: body { cursor: pointer; } //touch anywhere to end a focus
Notes
place plugin before bootstrap.js to avoif affecting tooltips
only tested on iphone XR ios 12.1.12, and ipad 3 ios 9.3.5, using Safari or Chrome.
References:
https://code.jquery.com/ui/
https://api.jquery.com/category/selectors/jquery-selector-extensions/
Related
I have an img for a large screen and another for small screens.This is an easy option and if works but I am not sure if there it is a good practice to put the same img in different sizes in the html and hide one with display none? is there any other problem with that option?
CSS:
#small {
display:none;
}
#media screen and (max-width: 630px) {
#big { display:none; }
#small { display:block; }
}
HTML:
<img id="big" src="img/1-big.jpg">
<img id="small" src="img/1-small.jpg">
IMUO I think this is not a good practice, because you are loading all the images twice (and hidden then). If you are using bootstrap (or responsive page) you could use the class img-responsive or this:
img {
width: 100%;
height: auto;
}
Or if not, you could do this:
/* For width smaller than 400px: */
body {
background-image: url('1-big.jpg');
}
/* For width 400px and larger: */
#media only screen and (min-width: 400px) {
body {
background-image: url('1-small.jpg');
}
}
Doing that way, you only load the image when needed and avoid load twice the images. Another example as background image: https://www.smashingmagazine.com/2013/07/simple-responsive-images-with-css-background-images/
I don't see anything wrong with this. In fact, it is a recognised technique to reduce page load times and to keep page sizes down on mobile (providing, of course, that you only load whichever image is required for your device size).
Also note, as the only potential pitfall I can see with this, is that that simply setting the CSS property to display: none does not always prevent an image from loading (see here: Does "display:none" prevent an image from loading?)
An alternative to this would be to have images stored with the same name and a small or no suffix (for larger images) added to them (almost like you have in your example), except only have 1 html element on the screen at any one time and modify the paths using javascript. Example;
// HTML ELEMENT
<img class='thumbnail' src='img/thumb.png'>
// JAVASCRIPT
if(window.innerWidth < 640){
// This is for users with smaller screens, load the smaller image
var imgs = document.getElementsByTagName('img');
for(var i = 0; i < imgs.length; i++){
var current = imgs[i].getAttribute('src');
imgs[i].setAttribute('src', current + '-small.png');
// THIS WOULDN'T WORK AS IS, AS IT WOULD PRODUCE '.png-small.png'
// AND IS INTENDED **ONLY** TO ILLUSTRATE A CONCEPT
}
}
I recently encountered a.. "thing" in the land of SASS. And maybe you guys know a trick or something alike to "fix" it.
I've got this class .icon. It contains some basic styling for my icons (Used for an iconfont). These icons can then be placed in the markup whereever I want. For example in a button. But inside the button this icon needs some extra styling. So I do the following:
.icon {
// Basic styling
}
button {
.icon {
// Additional styling
}
}
It compiles to this css:
.icon {
// Basic styling
}
button .icon {
// Additional styling
}
Everything OK so far. But now I want to extend the .icon to an after-element inside of all my .foo elements like so:
.foo:after {
#extend .icon;
}
Now it compiles to this css:
.icon, .foo:after { // This is good, exactly what I want
// Basic styling
}
button .icon, button .foo:after { // But I don't need the second half of this line
// Basic Additional
}
Now the foo-element isn't just extending the "root" icon-class but also the icon-class under button and all its additional stylings. But I don't need that. I don't want that element to have that additional styling. It doesn't result in problems yet. But maybe that could happen later. So I was curious if it is possible to extend only the .icon from the root, omitting the nested .icon in the button, and possibly more nested icon-classes in other elements later on.
My first thought was to use an abstact class like %icon and extend from that, but the above mentioned icon-class, and the file that it is placed in, is generated by grunt-webfont. So I can't just change the icon-class styling 'cause its overwritten all the time.
What can I do? Is there some more to the extend function of SASS that I don't know of? Or is there a totally different way?
Thanks for any help.
SOLUTION:
Using all the awesome help and tips I found a way to avoid this problem:
Grunt-Webfont suggests to use the i-tag to display the icons. Font-Awesome does the same. So, I'm doing exactly that. And I usually don't use it for anything else.
This allows it to use the i-tag under the button for my extra styling, and not the .icon class. This way the .icon class is used only once in the generated file and then never again.
.icon {
// Basic styling
}
button {
i { // <= Previously '.icon'
// Additional styling
}
}
Have you tried doing something like this?
.icon {
//some styles from external (ie: grunt webfont)
color: red;
}
%icon {
#extend .icon;
}
button {
.ico {
#extend %icon;
//add some additional styles
}
}
.foo:after {
#extend %icon;
//add some more
}
You would then avoid generating the foo:after rule for the .icon inside the button.
EDIT2 - you'll need to create an additional class which you can use inside your styles, so there's only one .icon class defined (in your grunt-webfont generated css). Then just use the .ico class inside your styles and extend the %icon placeholder like shown above.
EDIT - have you considered solving this problem in your grunt-webfont generator?
From the documentation, it seems you can set the output to scss like so:
options: {
stylesheet: 'scss',
mixinPrefix: 'mixin-'
Then just use the mixin to define the styles of your desired classes?
I think this gets the result you're looking for? Albeit, slightly messily.
The method: make a placeholder style and extend that into .icon to begin with.
%icon-styles{
basic: styling;
}
.icon {
#extend %icon-styles;
}
.foo:after {
#extend %icon-styles;
}
button .icon {
#extend %icon-styles;
additional: styling;
}
It compiles into:
.icon, .foo:after, button .icon {
basic: styling;
}
button .icon {
additional: styling;
}
You can also use custom template with grunt-webfont. It’ll give you much more control on generated CSS.
I am new to html. Can I make a particular div in my web-page appear in fullscreen when I press alt and space keys? This is my code.
<!DOCTYPE html>
<html>
<body>
<p>This is some text.</p>
<div style="color:#0000FF">
<img src="./1.jpg" height="42" width="42">
</div>
</body>
</html>
Use the full-screen pseudo class (for webkit and mozila) :
:-webkit-full-screen {
/* css rules for full screen */
}
:-moz-full-screen {
/* css rules for full screen */
}
See this Mozilla article and this David Walsh article for usage
HTML and CSS provide no means to trigger full screen mode for an element. JavaScript is your only option.
HTML 5 introduces a full screen API for JavaScript. It is still experimental, so you need to use prefixed property names in some browsers and it won't work at all in others.
function makeFullScreen(element) {
if (element.requestFullScreen) {
element.requestFullScreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullScreen) {
element.msRequestFullScreen();
}
}
You then just need to bind an event handler to call it.
document.addEventListener('keypress', function (evt) {
if (evt.altKey && evt.keyCode === 32) {
makeFullScreen(document.querySelector('div'));
}
});
Beware of depending on modifier keys though. On my system, alt + space is captured at the OS level (to open Spotlight) so it will never reach the browser.
I have a list of <div>s. Each <div> has class zebra. Until now I've used the following to stripe the list:
.zebra:nth-child(2n) { /* colors */ }
Now I'm implementing a filtering feature, such that some of these <div>s will have a class hidden. I tried updating my css to
.zebra:not(.hidden):nth-child(2n) { /* colors */ }
But that had no effect. What am I missing? How can I combine these selectors so that only the showing .zebra <div>s are considered in the :nth-child(2n)?
Here's a fiddle of what I'm describing.
UPDATE:
there is an unknown number of .hidden elements, and an unknown total number of elements. (the list is data-driven, not static).
I'd really rather not do any of:
run a javascript every time a filter control is touched, just to re-color the showing list items.
remove an element entirely when it's hiding. this makes re-adding it non-trivial (afaict).
Instead of removing the element as Justin suggested, you could replace it with an element of a different tag. We could use details, for example:
var placemarker = document.createElement("details");
node.parentNode.replaceChild(placemarker, node);
placemarker.appendChild(node);
Then, instead of using :nth-child, use :nth-of-type.
details { display:none; }
div.zebra:nth-of-type(2n) { /* colors */ }
Unhiding the element can then be done with:
placemarker.parentNode.replaceChild(placemarker.firstChild);
See this static example.
if you don't mind delving into jquery..
$('#yourHiddenElement').remove();
will remove it so that your css shades alternate.
http://jsfiddle.net/NYvcv/1/
I would suggest using this instead of applying the class 'hidden' to the element you want to hide.
If you know there will be a limited number of .hidden items, you can do something like this:
.zebra2:nth-child(2n) {
background: lightgrey;
}
.zebra2.hidden ~ .zebra2:nth-child(2n) {
background: inherit;
}
.zebra2.hidden ~ .zebra2:nth-child(2n+1) {
background: lightgrey;
}
.zebra2.hidden ~ .zebra2.hidden ~ .zebra2:nth-child(2n) {
background: lightgrey;
}
.zebra2.hidden ~ .zebra2.hidden ~ .zebra2:nth-child(2n+1) {
background: inherit;
}
And so on. This particular example breaks if there are more than 2 hidden items.
One possible solution:
use jQuery to change the .hidden element's type to, say, <li>. Use :nth-of-type instead of :nth-child.
http://jsfiddle.net/Nb68T/1/
Working example: http://jsfiddle.net/JVVcA/
HTML:
<fieldset id="data-page">
<legend>data-page</legend>
<button rel="page1">Highlight page one</button>
<button rel="page2">Highlight page two</button>
<div data-page="page1">
<h1 id="page1">Page one</h1>
<h1 id="page2">Page two</h1>
</div>
</fieldset>
<fieldset id="class">
<legend>class</legend>
<button rel="page3">Highlight page three</button>
<button rel="page4">Highlight page four</button>
<div class="page3">
<h1 id="page3">Page three</h1>
<h1 id="page4">Page four</h1>
</div>
</fieldset>
CSS:
fieldset { border: 1px solid #aaa; padding: 5px; }
h1 { background-color: white; }
div[data-page="page1"] h1#page1 { background-color: pink; }
div[data-page="page2"] h1#page2 { background-color: pink; }
div.page3 h1#page3 { background-color: cyan; }
div.page4 h1#page4 { background-color: cyan; }
JS:
$('#data-page button').click(function(){
var rel = $(this).attr('rel');
$(this).siblings("div").attr('data-page', rel);
});
$('#class button').click(function(){
var rel = $(this).attr('rel');
$(this).siblings("div").attr('class', rel);
});
Initial load:
After clicking "Highlight page two" and "Highlight page four" in Webkit (specifically, Google Chrome stable Windows 7):
After doing the same in Firefox:
As you can see, the data-page selector works fine on the initial rendering of the of the page, but when the DOM is manipulated on the fly, styles defined by the [data-page="???"] CSS selector are not affected accordingly. Compare this to the situation with the class selectors. When classes are changed on the fly, the styles change as expected.
A possibly related note is that I've encountered cases while using this attribute selector in conjunction with CSS transitions where a similar lack of responsiveness happens, but on those cases, clicking elsewhere on the page, waving your mouse around, or just waiting for a bit eventually results in the expected change going through.
So is there a way around this other than to just throw up your hands and not use data-page-style attributes?
It's the same issue that's applied for the ~ or multiple + selectors and pseudo-classes in webkit: this kind of selectors are rendered only once and the last time I checked the relevant bug reports in webkit's tracker, they stated that it works like intended.
But, some people had found the fix, but it's really is overhead: to add always-reflowing property to body, so it's must be added only to those elements, where something changes, the divs inside field sets for your example.
So, there is a fixed fiddle: http://jsfiddle.net/JVVcA/2/
And these are the styles for fixing such problems:
/* The `fixing` animation */
#-webkit-keyframes bugfix { from { padding: 0; } to { padding: 0; } }
.anElementToFix { -webkit-animation: bugfix infinite 1s; }
Note that you must add the fix to the element whose attribute is can be changed, not the targeted by selector elements.
My version of workaround.
$('#data-page button').click(function(){
var rel = $(this).attr('rel');
var my_div = $(this).siblings("div");
my_div.attr('data-page', rel);
var my_html = my_div.html();
my_div.html(my_html);
});
$('#class button').click(function(){
var rel = $(this).attr('rel');
$(this).siblings("div").attr('class', rel);
});
Running an animation seems overly expensive.
Thanks to Zoltan Olah, I found a much more elegant, concise, and efficient workaround.
Simple toggle a nonsense class on the body. This will cause contained selectors to be re-evaluated.
You don't even have to define this class in CSS. Just applying it forces Safari to hunt through the page re-evaluating things.
Every time you change the attribute in question, toggle this class on or off to force the re-evaluation.
// change some attribute
$(".blah").attr("state", "otherState"); // example of changing an attribute (your app will be different)
$('body').toggleClass('zoltan'); // THIS IS THE LINE YOU MUST ADD EVERY TIME YOU CHANGE THE ATTRIBUTE