Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-transforms-2] Preserve-3d + backface visibility semantics need to be clarified #918

Open
smfr opened this issue Jan 13, 2017 · 29 comments

Comments

@smfr
Copy link
Contributor

smfr commented Jan 13, 2017

https://www.w3.org/Bugs/Public/show_bug.cgi?id=23015

@smfr smfr changed the title Preserve-3d + backface visibility semantics need to be clarified [css-transforms-2] Preserve-3d + backface visibility semantics need to be clarified Jan 13, 2017
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed backface visibility, and agreed to the following:

  • RESOLUTION: Add a new "pseudo-stacking context" definition and use it to define how backface-visibility works
The full IRC log of that discussion <heycam> Topic: backface visibility
<astearns> github: https://github.com//issues/918
<heycam> mattwoodrow: current spec says that backface-visibility only applies to the element it's on, not descendants
<heycam> ... doesn't say it would create any plane or layer, which Gecko faithfully implements
<heycam> ... other engines create a plane for all descendants but not positioned descendants
<heycam> ... which is not something there's any precedent for, seems a bit crazy. would prefer backface-visibility create a stacking context
<heycam> smfr: I think in WebKit it does create a stacking context, not a containing block
<heycam> mattwoodrow: maybe it is that it doesn't create a containing block
<heycam> chrishtr: definitely doesn't create a stacking context
<heycam> smfr: I'd be scared to change hte behavior of backface-visibility...
<heycam> mattwoodrow: so try to find a descroption of what it actually does. affects itself and non-positioned descendants?
<heycam> chrishtr: self painting layers reset the backface visibility thing
<heycam> mattwoodrow: don't know how to describe it for the spec
<heycam> chrishtr: similar to the set of things dbaron found that caused "painting as if positioned"
<heycam> ... in CSS 2.1, positioned elts paint later than regular elements
<dbaron> https://dbaron.org/css/test/2018/stacking-context-z-order
<heycam> ... anything that has an effect-y thing on it
<heycam> ... this is exactly what is on self painting layers
<heycam> ... so we could define it that way
<heycam> florian: where else did we run into this?
<heycam> chrishtr: might have been another case yes
<heycam> ... but the thing about hte paint order is one that's not specified properly, is that right?
<heycam> mattwoodrow: most of hte psecs that create stacking ocntext say that. but they mean create a stacking ocntext and sort it with positioned descendants
<heycam> chrishtr: overflow:hidden, backface-visibility, SVG elements and other atomically replaced elements, ...
<heycam> ... CSS clip probably
<heycam> ... and the rest of them do create stacking contexts
<heycam> ... we should define this, get interop on the table in dbaron's test, then use it for the backface-visibility definition
<heycam> dbaron: I don't think there's a whole lot in there that's not creating a stacking context
<heycam> ... CSS clip only applies to things that are positioned
<heycam> chrishtr: but in terms of the "painting as if positioned"
<heycam> ... I think overflow:clip is the most common
<heycam> dbaron: have we added that yet?
<heycam> chrishtr: I mean overlfow:hidden and scroll
<heycam> ... on the issue in which this table resides, I mention there's a silly quirk in Chrome and probably WebKit, not triggering self painting in some bizarre corner cases of scrolling
<heycam> smfr: WK does not create self painting layers for [...]
<heycam> dbaron: I don't htink Gecko sorts overflow:hidden on to positioned descendants list
<heycam> mattwoodrow: don't think so
<astearns> s/[...]/overflow:hidden/
<heycam> mattwoodrow: overflow:hidden isn't a stacking context, can interleave things inside and outside, so can't go in the positioned descendants list
<heycam> smfr: so is everything on the list make stacking context?
<heycam> mattwoodrow: yes
<heycam> chrishtr: overflow:hidden does not create a self painting layer (in Blink)
<heycam> ... if there's overlfow:scroll but is in effect like overflow:hidden since there's nothing to scroll, then we skip it
<heycam> ... that's something we could change in Chrome
<heycam> ... in any case, is this a good way to spec it?
<heycam> mattwoodrow: yes, I agree the CSS 2.1 spec describes floats [...]
<dbaron> I think Gecko calls this "pseudo-stacking context"
<heycam> RESOLUTION: Add a new "pseudo-stacking context" definition and use it to define how backface-visibility works
<heycam> dbaron: some of this would be easier if there's a spec to put this old CSS 2.1 text to evolve it
<heycam> ... maybe a central painting spec
<heycam> -- break until 4pm --
<TabAtkins> ScribeNick: TabAtkins

@smfr
Copy link
Contributor Author

smfr commented Sep 17, 2019

Until we have a new spec, css-transforms-2 should more clearly specify backface-visibility:hidden. Matt, would you like to take a stab at this?

@chrishtr
Copy link
Contributor

chrishtr commented Sep 20, 2019

There are two aspects to this issue: preserve-3d and backface-visibility.

For backface-visibility, there was a resolution earlier this week that backface-visibility:hidden should also apply to all descendants that are not pseudo stacking contexts (#4360).

@chrishtr
Copy link
Contributor

chrishtr commented Sep 20, 2019

For preserve-3d: we could use the same concept.

The current Gecko semantics are that the DOM ancestor of an element flattens if it does not have transform-style: preserve-3d.

Proposal which relaxes the above a bit: only elements that induce pseudo stacking contexts can cause flattening.

Thus this example would preserve 3d across the #middle div:

<div style="transform-style: preserve-3d">
  <div id=middle>
    <div style="transform: rotateX(100px)"></div>
  </div>
</div>

But not:

<div style="transform-style: preserve-3d">
  <div id=middle style='position: relative'>
    <div style="transform: rotateX(100px)"></div>
  </div>
</div>

This will probably improve interop, makes it a bit easier to structure content in some cases, and may be more consistent to reason about if we do the same thing for backface-visibility: hidden.

@chrishtr
Copy link
Contributor

Now for perspective CSS:

Blink and WebKit both use the enclosing stacking context ancestor that is composited. Therefore, behavior is a side-effect of compositing. :(

If an element has a 3D transform, then its nearest DOM ancestor with perspective is also composited (and because it has perspective it is also a stacking context). However, there may be an intermediate stacking ancestor that is below the perspective and above the transformed element.

Example 1 (perspective is applied to #inner in Blink and WebKit:

<!doctype HTML>
<div id=p style='perspective: 100px'>
  <div style="position: relative; will-change: transform">
    <div id=target style='transform: translateZ(-200px); background: lightblue; width: 100px; height: 100px'>
  </div>
</div>

Example 2 (no perspective applied in Blink (still does in WebKit, not sure why), only because of compositing.

<!doctype HTML>
<div id=p style='perspective: 100px'>
  <div style="backface-visibility: hidden">
    <div id=target style='transform: translateZ(-200px); background: lightblue; width: 100px; height: 100px'>
  </div>
</div>

@chrishtr
Copy link
Contributor

Blink should be able to change behavior to not depend on compositing any more for perspective, along with a corresponding change to preserve-3d.

@mattwoodrow
Copy link
Contributor

For backface-visibility, there was a resolution earlier this week that backface-visibility:hidden should also apply to all descendants that are not pseudo stacking contexts (#4360).

My understanding of the resolution was that backface-visibility:hidden would establish a pseudo-stacking context, such that the backface of the element and all descendants would be hidden, excluding descendants that are positioned or establish stacking contexts (full only, not pseudo).

@mattwoodrow
Copy link
Contributor

mattwoodrow commented Sep 21, 2019

The current Gecko semantics are that the DOM ancestor of an element flattens if it does not have transform-style: preserve-3d.

In my mental model, I consider an element with transform-style:preserve-3d, and a parent without (or with a flattening property) to create a 3d rendering context (rather than extend the parent's one). The flattening happens at the root of the 3d rendering context, so the element with preserve-3d is the one outputting a flat representation.

The subtle difference here is that it's clearer that two siblings with transform-style:preserve-3d and a transform-style:flat parent do not intersect with each other, since they're each create a separate 3d rendering context. If we consider the parent to be the one that flattens, then it's harder to explain why the two preserve-3d children don't intersect.

Proposal which relaxes the above a bit: only elements that induce pseudo stacking contexts can cause flattening.

Given the above, I think the required spec text to explain how to differentiate between transform:style:preserve-3d creating or extending a 3d rendering context is complex.

You have to walk the DOM tree (not containing block chain), looking for the first pseudo-stacking context, and check if that also has transform-style:preserve-3d. But, you also have to check the intermediate elements for a flattening property (like overflow:hidden), which does break the preserve-3d, but isn't a pseudo-stacking context.

For that reason, I'd much prefer to trial using the direct parent to check for preserve-3d, just since it's much simpler to explain and understand.

@mattwoodrow
Copy link
Contributor

For perspective, I think we could say the nearest (pseudo-)stacking context ancestor, since we don't need/want flattening properties in the middle to break it (and we're explicitly relying on that for the parallax scrolling effects).

I had a chat to @smfr on Friday, and we'd both still prefer to say the DOM parent though, since it's
simpler, and consistent with transform-style.

Would you be ok with trialing this behaviour, and we can reconsider the approach if there's serious webcompat breakage?

@chrishtr
Copy link
Contributor

My understanding of the resolution was that backface-visibility:hidden would establish a pseudo-stacking context, such that the backface of the element and all descendants would be hidden, excluding descendants that are positioned or establish stacking contexts (full only, not pseudo).

Sure, but this is the same as what I said, no? The descendants that are positioned or establish stacking contexts is nearly the same as pseudo stacking context.

@chrishtr
Copy link
Contributor

You have to walk the DOM tree (not containing block chain), looking for the first pseudo-stacking context, and check if that also has transform-style:preserve-3d. But, you also have to check the intermediate elements for a flattening property (like overflow:hidden), which does break the preserve-3d, but isn't a pseudo-stacking context.

Oh right. I had forgotten about the conflict of overflow:hidden not inducing a pseudo stacking context, yet still flattening. This is a good argument in favor of the DOM parent approach.

Would you be ok with trialing this behaviour, and we can reconsider the approach if there's serious webcompat breakage?

Yes! For sure I'm ok with trialing this behavior. I want clear and simple semantics also. :)

I only suggested the loosening because when I wrote it it seemed simple (and I forgot about overflow:hidden, as noted above). But I think it's not, because of the reasons you outlined.

@chrishtr
Copy link
Contributor

chrishtr commented Apr 3, 2020

For perspective, I think we could say the nearest (pseudo-)stacking context ancestor, since we don't need/want flattening properties in the middle to break it (and we're explicitly relying on that for the parallax scrolling effects).

Could you clarify what you meant by "explicitly relying on that"? Do you mean we're relying on support for intermediate non-stacking context elements between the perspective and transformed elements?

@flackr for input also.

I had a chat to @smfr on Friday, and we'd both still prefer to say the DOM parent though, since it's
simpler, and consistent with transform-style.

I agree it's simpler and consistent.

Would you be ok with trialing this behaviour, and we can reconsider the approach if there's serious webcompat breakage?

Need to check parallax use-cases first..

@chrishtr
Copy link
Contributor

chrishtr commented Apr 3, 2020

Would you be ok with trialing this behaviour, and we can reconsider the approach if there's serious webcompat breakage?

Need to check parallax use-cases first..

Yes will trial it (discussed offline with @flackr)

@mattwoodrow
Copy link
Contributor

Rather than specifying the 'pseudo stacking context' concept as discussed earlier, I'd like to suggest a different approach:

backface-visibility: hidden should create a regular stacking context, and become a containing block for position:fixed descendants, if it participates in a 3D rendering context.

For the majority of cases, this should be a no-op, as elements with backface-visibility:hidden are generally already transformed (and thus are a stacking context and containing block), or are not part of a 3d context (the property is frequently used a 'compositing' hint, unrelated to its original intent).

For the small number of cases where an untransformed element has backface-visibility:hidden, but is expecting to still be rotated by an ancestor with preserve-3d, this makes the behaviour much simpler and easy to explain.

There's still some complexity, in that the backface that is hidden is a plane containing the element and all descendants, with the exception of children that are participating in the 3d rendering context themselves. I think that's an existing problem with the spec though, that the concept of what exactly planes get formed is still undefined.

@dbaron
Copy link
Member

dbaron commented Aug 29, 2023

I think this seems like a good approach. A few comments:

  • I think the definition of when backface-visibility: hidden needs to create a stacking context should have two parts: either (a) if it participates in a 3D rendering context or (b) it has a 3D transform. This is needed since backface-visibility does work on elements with 3D transforms even if they're not in a 3D rendering context (see example).
  • As to the question of what is in that stacking context (and hidden if the backface is hidden): I think the idea of all descendants in the plane, but not other descendants, is a good one. It's sort of an unusual thing to specify... but I think maybe it's also sort of how the stacking context established by transform itself works.
  • One important question with this proposal is what makes content "in the plane" versus "out of the plane" (since that now controls both whether it's in the stacking context and whether the backface-visibility: hidden applies to it). I'd suggest maybe this should be defined based on whether the transform function is a 3D transform function or not. This has the advantages that:
    • we don't need to worry about mathematical precision issues for whether a transform is flat or not and
    • it avoids discontinuity at the ends of animations (for example, if the endpoints of an animation are flat but the middle is 3D, which I think is a reasonable setup and probably not an unusual use of 3D transforms) and
    • it gives authors a relatively straightforward way to escape from the stacking context.

We probably need to do some measurement/testing of whether we're going to have any compatibility problems with this approach, but it seems like a sensible plan and one that has a reasonable chance of being sufficiently compatible.

@chrishtr
Copy link
Contributor

@mattwoodrow I like this approach, thanks for driving it!

@mattwoodrow
Copy link
Contributor

I think this seems like a good approach. A few comments:

  • I think the definition of when backface-visibility: hidden needs to create a stacking context should have two parts: either (a) if it participates in a 3D rendering context or (b) it has a 3D transform. This is needed since backface-visibility does work on elements with 3D transforms even if they're not in a 3D rendering context.

I think that's fine, but transform already creates a stacking context, so the second half is a no-op (but maybe useful clarity regardless).

  • As to the question of what is in that stacking context (and hidden if the backface is hidden): I think the idea of all descendants in the plane, but not other descendants, is a good one. It's sort of an unusual thing to specify... but I think maybe it's also sort of how the stacking context established by transform itself works.

Also agreed! I think we need to define this as part of transform (or maybe transform-style, since it's preserve-3d that causes this to deviate from the normal stacking context definition). backface-visibility can then make reference the above as to what exactly it's hiding the backface of.

  • One important question with this proposal is what makes content "in the plane" versus "out of the plane" (since that now controls both whether it's in the stacking context and whether the backface-visibility: hidden applies to it). I'd suggest maybe this should be defined based on whether the transform function is a 3D transform function or not. This has the advantages that:

    • we don't need to worry about mathematical precision issues for whether a transform is flat or not and
    • it avoids discontinuity at the ends of animations (for example, if the endpoints of an animation are flat but the middle is 3D, which I think is a reasonable setup and probably not an unusual use of 3D transforms) and
    • it gives authors a relatively straightforward way to escape from the stacking context.

Does that mean for this example:

<div style="transform: rotateX(180deg); backface-visibility: hidden; background-colour: blue; transform-style: preserve-3d">
    <div style="transform: scaleZ(2); background-colour: red"><div>
    <div style="transform: scaleX(2); background-colour: green"><div>
</div>

We'd see just the red colour?

I guess that means that the green element doesn't really participate in the 3D rendering context.

We probably need to do some measurement/testing of whether we're going to have any compatibility problems with this approach, but it seems like a sensible plan and one that has a reasonable chance of being sufficiently compatible.

Fingers crossed it works out! I'll aim to implement the current plan in WebKit, behind a testable flag and can go from there.

mattwoodrow pushed a commit to mattwoodrow/csswg-drafts that referenced this issue Sep 13, 2023
…context and containing block when participating in a 3D rendering context. (w3c#918)
mattwoodrow added a commit that referenced this issue Sep 13, 2023
…context and containing block when participating in a 3D rendering context. (#918) (#9348)

Co-authored-by: Matt Woodrow <[email protected]>
@dbaron
Copy link
Member

dbaron commented Oct 6, 2023

The discussion in #3305 was relatively unfavorable to making distinctions based on syntactic 3D-ness, and the spec currently disallows it for the individual transform properties, although not for transform itself. Maybe we still want the syntactic 3D distinction for transform, but maybe not?

@mattwoodrow
Copy link
Contributor

I feel like that case is somewhat different (in my mind, at least), in that UAs were using the presence of a 3d transform function as an internal heuristic for which rendering path to use (which also had very subtle rendering differences due to antialiasing), and there was resistance to leaking that into the spec since it should just be an implementation detail.

In this case, the 3d-ness is super relevant to what we're trying to define, so I think it's more reasonable to put into the spec. I agree that using the 3D functions rather than the computed matrix has more predictable behaviour.

Regardless, this might be worth putting on the Agenda to discuss more widely.

@dbaron
Copy link
Member

dbaron commented Oct 12, 2023

Based on https://bugs.chromium.org/p/chromium/issues/detail?id=954591#c21 I think we may also need to include having transform-style: preserve-3d as a condition for applying its own backface-visibility rather than using the parent's. (I think that's what we're going to try to do for now, to fix the case that's in Interop2023, but it seems like something that we may also be stuck with.)

@dbaron dbaron added the Agenda+ label Oct 12, 2023
@mattwoodrow
Copy link
Contributor

Should this all be described as part of the 3D transform rendering model?

The current description is that 'An element participates in a 3D rendering context if its parent establishes or extends a 3D rendering context.'

Should that be somewhat changed to describe the various planes that are created?

I think we're suggesting that all elements that create or extend a 3D rendering context get a plane, as do elements that have a 3d transforms and have a parent that creates or extends a 3D rendering context. Everything else is included in the plane of the rendering context (as normal for stacking contexts).

@dbaron
Copy link
Member

dbaron commented Oct 18, 2023

I should think about your most recent comment -- but before I forget I want to add a clarification to #918 (comment) . It's actually a little more complicated because I think the additional condition for applying an element's own backface-visibility is that the parent has transform-style: preserve-3d and the element has a transform. (But the key difference is that in this case it doesn't require that the transform be 3D in any way.) We discussed a few testcases related to this in https://chromium-review.googlesource.com/c/chromium/src/+/4935628 and once we actually settle on a spec we should make sure to add those testcases (with correct rendering results!) to WPT.

@mattwoodrow
Copy link
Contributor

Can you explain a bit more about why we need that restriction?

If we said that an element with a 2d transform (and a transform-style: preserve-3d parent) isn't participating in the 3d rendering context, and is just part of the parent's plane, and thus backface-visibility: hidden didn't apply, what would break?

@dbaron
Copy link
Member

dbaron commented Oct 19, 2023

I think the concern was that in this testcase the green rectangle is interoperably shown and we were hesitant to change that, at least in the same change that's needed to fix the Interop2023 failure. (That said, in Chrome it's shown because of the will-change: transform on the inner div, in Safari because of the transform on the inner div, and in Firefox because of either.)

@mattwoodrow
Copy link
Contributor

I don't see the green div in Safari if I drop the will-change.

Should will-change: transform be considered a '3d property' (since we can't know what the transform function will be, and the author is probably wanting a 'compositing' hint)?

@emilio
Copy link
Collaborator

emilio commented Feb 14, 2024

cc @glennw @jrmuizel

@emilio
Copy link
Collaborator

emilio commented Feb 14, 2024

backface-visibility: hidden should create a regular stacking context, and become a containing block for position:fixed descendants, if it participates in a 3D rendering context.

Should this be unconditional? Otherwise, changing preserve-3d on an ancestor might need various tree changes around, right?

@dholbert
Copy link
Member

(cc @tnikkel as well)

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-transforms-2] Preserve-3d + backface visibility semantics need to be clarified.

The full IRC log of that discussion <fantasai> dbaron: Not looking for a decision, wanted to summarize what's going on to make sure people are aware.
<fantasai> dbaron: The underlying issue in this old issue is that we have backface-visibility
<fantasai> dbaron: which hides things when facing backwards in 3D rotation
<fantasai> dbaron: but we don't have a good definition of what this means
<fantasai> dbaron: fundamental thing we're missing is "what is *this* that you are hiding" and "which descendants do you consider"
<fantasai> dbaron: Discussed with Matt Woodrow, basic proposal in the issue
<fantasai> dbaron: Matt wants to try prototyping, hopefully it won't break the world
<fantasai> dbaron: and if it works, we'd like to specify backface visibility this way
<fantasai> dbaron: it will require changes to all implementations
<fantasai> dbaron: These are things that are not particularly interoperable, or particularly sensible.
<TabAtkins> (I think this is the proposal (+ some clarifying comments in following comments) https://github.com//issues/918#issuecomment-1696711327)
<fantasai> dbaron: if you care about this feature, pay attention to this
<fantasai> dbaron: Beyond that, if people would like a summary I can try
<fantasai> dbaron: but gets very technical very quickly
<fantasai> Rossen_: Do we have the right people?
<fantasai> dbaron: Don't know person from Gecko
<fantasai> dholbert: Timothy Nickel, probably
<dholbert> s/Nickel/Nikkel/
<fantasai> dbaron: The two people both involved are both former Gecko now involved in other engines (Blink, WebKit) :)
<fantasai> dbaron: when you set backface-visibility: hidden, one question is which descendants do you apply to and which not
<fantasai> dbaron: we don't want to apply to a descendant that itself has a 3D transform
<fantasai> dbaron: because it might be rotated more and be visible. So it should be independent
<fantasai> dbaron: For starters, this probably is required by Web-compat; but probably also sensible behavior
<fantasai> dbaron: My suggestion was that we distinguish based on syntax of tranform
<fantasai> dbaron: i.e. whether you are 3D is based on whether you write translate() or translate3D()
<fantasai> dbaron: we got rid of such distinctions in the past, but I think it's the right thing to do here
<fantasai> dbaron: 1. [missed]
<fantasai> dbaron: 2. Can avoid discontinuity at animations
<fantasai> dbaron: you don't want backface-visibility participation to pop during the animation
<emilio> q+
<fantasai> dbaron: 3. It gives authors an escape hatch.
<fantasai> TabAtkins: TypedOM preserves that distinction for legacy reasons, but can continue to preserve
<fantasai> emilio: interpolation as a matrix?
<fantasai> dbaron: Your interpolation is either matrix() or matrix3D()
<fantasai> emilio: if it's preserved in the computed value, then seems reasonable
<fantasai> dbaron: Not ready to take a resolution yet
<fantasai> emilio: When you have matched transform lists, basically coalesce into something that assumes 3D, idk if that's the case in Chromium or WebKit
<fantasai> emilio: that may make this trigger unexpectedly
<Rossen_> ack emilio
<fantasai> dbaron: might need to audit some of those codepaths
<fantasai> dbaron: we removed some distinctions, might need to revisit
<fantasai> emilio: Part of the proposal makes backface-visibility be a CB for fixed descendants, but only in some cases?
<fantasai> emilio: that seems a bit annoying
<fantasai> emilio: changes to preserve3D ancestors could end up reparenting things in the box tree
<fantasai> emilio: doable but not fun
<fantasai> dbaron: We wrote it that way because strong suspicion it wouldn't be Web-compatible otherwise
<fantasai> emilio: because of [missed]
<fantasai> dbaron: Matt might remember more
<fantasai> dbaron: If we want telecon time, should schedule a separate breakout time, because we don't overlap on the two regular call times

@astearns astearns removed the Agenda+ label Feb 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Tuesday afternoon
Development

No branches or pull requests

8 participants