Open trusktr opened 2 months ago
.some-ancestor *:not(.some-descendant) { opacity: 0.5 }
is quite straightforward. Besides, opacity
applies on a flat rendering of the ancestor and its children, so I fear opacity-override
could not be applied:
Conceptually, after the element (including its descendants) is rendered into an RGBA offscreen image, the
opacity
setting specifies how to blend the offscreen rendering into the current composite rendering.
.some-ancestor *:not(.some-descendant) { opacity: 0.5 }
That's not the same, that will apply to anything that is nested, which can totally break the desired effect.
Besides,
opacity
applies on a flat rendering of the ancestor and its children, so I fearopacity-override
could not be applied:
I'm sure it is possible to update the spec so that opacity overrides are applied within the offscreen image in a step prior to compositing that offscreen ancestor, etc.
The issue here is that descendants' opacities aren't "derived from multiplying with ancestor opacities". A descendant with opacity: 1
has opacity 1 - it's fully opaque. But the partially-transparent ancestor draws all of its descendants onto itself, as a single graphics layer, then applies opacity to that single resulting image. This is very very different from each element painting itself partially-transparent and then all stacking together (which is roughly what happens with @cdoublev's example code).
Because everything is composited together into a single image, if you want something to not be composited with the group, it has to exist fully outside of the group - either completely above everything or completely below everything. Outside of some rare exceptions (top layer, mostly), the DOM's painting model implies that the element isn't a descendant of the partially-transparent ancestor at all.
Fundamentally, what's going on here is the difference between group opacity and item opacity. You can find tons of tutorials explaining the same thing (and people asking this exact same question) for graphics programs.
Just a quick note that while I'm not sure if this is the best solution, big +1 for addressing the pain point: I've definitely had this problem numerous times.
Adding to what @tabatkins mentioned above, the only way this could be achieved, I think, is if we could instruct the browser to somehow scope elements by isolating them into separate layers, kind of like @scope
does for styles, and then the browser should somehow "merge" these layers, instead of compositing them, kind of like feMerge
does.
But this is just complete theoretically sci-fi talking.
Another sci-fi talking: allow multiple layer trees / paint property trees with non-similar structures. For example, tree 1 for transforms where the .some-descendant
is in .some-ancestor
's layer, and tree 2 for opacity where .some-descendant
breaks out of .some-ancestor
's subtree.
@tabatkins yeah I understand the spec specifies that, but it is something I bet most people don't know, and the behavior most people see most of the time is basically the equivalent of a simple multiplicative opacity in terms of mental model.
Is there a way to make an element break out of the graphics layer into its own (while the parent has opacity < 1)?
How about updating the spec so that at least transform-style:preserve-3d
content is not flattened (so it actually preserves 3D like it says!) by breaking out into a new layer, and is also not affected by ancestor opacity (the layer opacity starts over at 1)?
This would solve the OP because to "highlight" an element you could break it out in 3D space (even just a small amount that is not noticeable by the naked eye) and it would be its own new graphics layer with its own opacity, without having to change the shape of the DOM tree and using only CSS styling.
This would also be more like how actual 3D engines work. Flattening, f.e. as if setting a 3D node's scale.z to zero, when the tree node has opacity < 1, is nonsensical in all 3D engines that I know of. This is very surprising behavior for 3D in CSS.
Sometimes we'd like to make a parent/ancestor have opacity less than one, but would like some child not to be affected or to have a different opacity.
This can sometimes be achieved with
background-color: rgba()
when it is specifically the background content that should have different opacity than children, but it solves the problem only part of the time.It would be nifty to have a way to override the opacity of a child such that it is not multiplied with any ancestor opacity, but instead starts with a value as if ancestor opacities are all 1.
For example, currently, the following results in an ancestor having visual opacity 0.5, and a descendant having visual opacity of 0.4:
With a new property named
opacity-override
(or something) we could force the descendant to have a specific opacity not derived from multiplying with ancestor opacities:This would be very useful for cases when we'd like to dim all content except to highlight a particular piece of the content, without having to resort to drastic changes to DOM structure (someone may only be editing CSS, and does not want to have to refactor code spread across UI components, for example). In this use case, we might want to use opacity override
1
so that all content in some ancestor is faded out except for some highlighted descendant: