w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.5k stars 666 forks source link

Proposal for custom matched sets of elements #7679

Open romainmenke opened 2 years ago

romainmenke commented 2 years ago

When looking at the API for Highlights I drew a parallel to a set of problems I often encounter.

  1. Applying styles on elements for states that can only be determined through Javascript
    1. Element is 50% or more visible
    2. Page is scrolled
  2. Creating polyfills for selectors
    1. :focus-within
    2. :has()
    3. ...

Both sets of problems require developers to take a detour with DOM manipulation.

The pattern is mostly the same for each case. We will create an event listener with the relevant API (scroll event listener, IntersectionObserver, ...) and then add a class or attribute on certain elements.

In CSS we would write styles for certain elements that have this class or attribute.

Changing the DOM is orthogonal to the intended result. You want to make a known set of elements match certain CSS rules.

With the API for Highlights I was wondering if we could have something similar for matching elements.

I am using node set as a placeholder for a good name. It's intended meaning is a set of elements/nodes that all match the corresponding CSS selector.

:node-set(my-custom-set) {
    color: pink;
}
const nodeSet = new NodeSet(); // Same API as `Set`

CSS.nodeSets.set('my-custom-set', nodeSet);

// Use any event or callback available in JS to add or remove elements from the set.
// - `addEventListener('scroll', ...)`
// - `IntersectionObserver`
// - ...
nodeSet.add(document.getElementById('alpha'));
nodeSet.add(document.getElementById('beta'));

The main benefit of a feature like this would be the ability to leave the DOM as-is while still applying styles based on state.

I don't think this will be useful for every case where developers now add a class or attribute with Javascript. I think it could be a more low level API that allows polyfills and frameworks to apply styles without touching the DOM.

Changing the DOM can have unintended side effects and can cause performance issues.

The names used in this proposal need work, but bike-shedding can happen later.

tabatkins commented 2 years ago

What is the practical difference between this proposal and just using a class? The "performance issues" with changing the DOM are related to invalidating styles for affected elements, which would be exactly the same with this, yeah? (And, unless this is internally implemented using the same descendant-caching optimizations that classes currently use, the invalidation cost will be worse than just modifying classes.)

Highlights needed a special mechanism because they don't exist in the DOM at all, so we needed to invent something outside of it to refer to them. But that just isn't the case here. Is there another issue with modifying classes that I'm missing?

romainmenke commented 2 years ago

The "performance issues" with changing the DOM are related to invalidating styles for affected elements, which would be exactly the same with this, yeah? (And, unless this is internally implemented using the same descendant-caching optimizations that classes currently use, the invalidation cost will be worse than just modifying classes.)

Performance issues I had in mind were more user land. Frameworks and libraries tends to "watch" the DOM for state changes. Performance is a secondary concern.

Is there another issue with modifying classes that I'm missing?

The main issue is that sometimes DOM manipulation is unexpected or unintuitive. For example in the polyfill for :has() we add attributes on matching elements.

Consumers of the polyfill do not expect this behaviour. They expect the polyfill to leave the DOM as-is.

Examples of how these DOM manipulations look :

Screenshot 2022-09-08 at 00 39 21

warning: this clip contains flashing content

https://user-images.githubusercontent.com/11521496/188995666-4e29682e-098a-4b60-b559-9fbcf7a378a2.mp4

This polyfill also illustrates how there isn't a performance issue with DOM manipulation in browsers themselves. Even though it is a naive implementation of :has() it is still fast enough to be useful as far back as IE 9.

bramus commented 2 years ago

Could this be covered by @custom-selector?

@custom-selector :--my-custom-set #alpha, #beta;
romainmenke commented 2 years ago

It shares some aspects with script based custom selectors : https://drafts.csswg.org/css-extensions-1/#script-custom-selectors and that might be the better overal solution.

(If I am reading the notes in issue 2 correctly)

One point where they fundamentally differ is style invalidation. This proposal is fully manual where script based custom selectors assume that browsers know when to re-check if a certain element might change from matching to not matching or the other way around.


update :

@custom-selector :--my-custom-set #alpha, #beta;

:--my-custom-set {
  color: green;
}

These exact selectors would still require a DOM change before the rule using it would match.