0

How do I add arbitrary selectors in CSS rules?

For instance, say I want to make every item of the .effect class turn red if the user hovers over#target. How would I implement this? Is there a robust approach to this? I know about things like nesting .effect inside #target or using the sibling selectors ~ and +, but all of these rely on a certain way of structuring the HTML.

Is this possible at all? It seems like something relatively straight forward. If it's not possible, is there any reason it isn't?

I do not want to use Javascript.

Jeroen
  • 15,257
  • 12
  • 59
  • 102
  • what is the structure of your HTML? – Daniel_ZA Feb 22 '17 at 10:06
  • 1
    @Vucko No, this does not effect `.effect` elements _outside_ `#target`. – arkascha Feb 22 '17 at 10:09
  • @Daniel_ZA: I am looking for a layout-agnostic solution. I already know how to do this when they are nested or siblings. – Jeroen Feb 22 '17 at 10:10
  • @arkascha yes, and I said if the `.effect` is the child of the `#parent` element. However, without the OP's HTML is hard to tell what he needs. – Vucko Feb 22 '17 at 10:10
  • 1
    @Vucko The OP explicitly wrote that he knows how to do that with `.effect` being nested or a sibling. So the question clearly is about the issue of elements _outside_ `#target`. – arkascha Feb 22 '17 at 10:11
  • @Vucko I did not include any HTML because this question asks to seek for an HTML-agnostic solution if one exists, and if it doesn't, why it doesn't. – Jeroen Feb 22 '17 at 10:12
  • @JeroenBollen You'd have to use Javascript. For performance reasons, CSS selectors are actually tested "backwards" bubbling up the DOM. All hierarchial selectors require elements to have a direct parent relationship. – Luke Briggs Feb 22 '17 at 10:13
  • 2
    I'd say you need to set some global state by means of client side logic (javascript), since css does not offer upwards iterations due to its "cascading" nature. You'd need something like a `having()` selector for that which does not exist. – arkascha Feb 22 '17 at 10:13
  • @LukeBriggs If you could post that as an answer... :) – Jeroen Feb 22 '17 at 10:14
  • @arkascha Even `having` would not provide ability to control any random element based on any other. –  Feb 22 '17 at 11:00
  • @torazaburo I'd expect it would, but maybe I am wrong. Would you care to elaborate? – arkascha Feb 22 '17 at 11:01
  • `having` or `has` would select an element which has a matching child or some sort. –  Feb 22 '17 at 14:03

1 Answers1

4

No, you can't.

Don't expect it for the forseeable future either. Let's take a look why!

From the point of view of someone who works on a CSS engine, selectors are actually evaluated backwards. This is certainly a rather interesting and less known aspect of CSS; Whilst the CSS selector specification does not directly define the implementation behaviour, all selectors are defined with this in mind. No hierarchial/ 'structural' selector has been created which can arbitrarily jump around the DOM as that would cause major performance issues in comparison to what we have today.

So, for example, let's take the following selector:

#target:hover .effect

This requires that an element with a class of effect is a child (at any depth) of an element with an ID of target because the selector engine starts by matching elements with a class of effect first, then proceeds to work backwards, stepping up the DOM looking for a parent element with an ID of target next.

Jumping to the parent node is extremely fast. Evaluating this in the forward direction would involve testing all children of any element with an ID of target which is considerably more performance intensive.

This characteristic of CSS evaluation is naturally important for performance; at the worst case, the above selector will only bubble up to the root of the DOM, testing only a handful of elements along the way. The direct child selector, a > b, only tests the direct parent and then stops, for example.

'Baking' the structure of a selector

For even further performance, the structure of a selector is 'baked' into the DOM. There certainly isn't consensus on this, i.e. every CSS engine does it differently, but roughly when the DOM structure of a selector matches (i.e. we have found an element with a class of effect and any parent with an id of target) the selector is recorded as having matched in the DOM, regardless of the hover state on #target. When the hover state on #target changes, it then simply bumps all the selectors that are baked at the element - this may then trigger the whole selector to activate or deactivate. This way we're not constantly testing masses of elements when the mouse moves around, for example.

In short, none of this works either if it could arbritarily jump around the DOM. Elements entering/ leaving the DOM could affect selectors in entirely separate parts of the DOM, so the style engine would potentially be checking the entire DOM to keep this index up to date.

Partially loaded DOM

Also consider that we can test for elements before something, but not after (when evaluated backwards):

h1 + h2
h1 - h2 /* ..? Doesn't exist! */

This is because when we test this particular selector starting against a 'h2' element, the DOM following it might not actually be loaded yet. As soon as an element enters the DOM, either because it's just been parsed from the incoming HTML or it has been added via scripting, we begin checking for which selectors it matches. That means we can't depend on anything being available after the element in the raw HTML, so again this would also be a block for any arbritary DOM hopping.

In Summary

It's currently not possible and it's unlikely to be added any time soon because it invalidates multiple performance characteristics of CSS implementations. That's not to say that it won't be added however; the W3C does appreciate that hardware is getting ever more powerful, so there is always a point at which author convenience will win over implementation performance considerations.

So, putting this a little further into context of the question, take a look at this fiddle created by @Marvin to see what's currently possible with selectors today.

Community
  • 1
  • 1
Luke Briggs
  • 3,745
  • 1
  • 14
  • 26
  • 2
    +1. I've deleted my answer as yours is much better. I just want to expand your answer with a [fiddle showing what currently can be selected](https://jsfiddle.net/216zaaLo/). Maybe some readers might be interested. – Marvin Feb 22 '17 at 11:03
  • 1
    @Marvin Thanks - I've just dropped that in at the end :) – Luke Briggs Feb 22 '17 at 11:10
  • Very comprehensive post! Exactly what I was looking for! Thanks for the effort! – Jeroen Feb 22 '17 at 11:44