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

Chrome 53's changes to CSS opacity break 3D transforms. #11

Open
trusktr opened this issue Sep 15, 2016 · 22 comments
Open

Chrome 53's changes to CSS opacity break 3D transforms. #11

trusktr opened this issue Sep 15, 2016 · 22 comments

Comments

@trusktr
Copy link

trusktr commented Sep 15, 2016

Check out this nasty problem Chrome 53 introduced (by following a flawed spec), which breaks ReFamous if you want to use opacity on a parent node that contains any children:

https://bugs.chromium.org/p/chromium/issues/detail?id=646993

Please leave a comment there to help persuade them to take back the changes.

You can reproduce the problem with ReFamous by make a parent node, adding a bunch of child nodes with arbitrary X,Y,Z positions, then apply an opacity to the parent node. In the parent node and child node make sure you have a DOMElement so that the result is a nested DOM structure when you inspect element.

What you'll see happen is that the child nodes will be flattened into a plane. This is a really bad design idea from the CSS-transforms specification.

When using mixed mode, then it will cause a nasty bug where the DOMElements are flattened, and the WebGL meshes are not.

@CompSciFutures
Copy link
Owner

Oh dear. Thanks for the heads up.

On 15 Sep 2016, at 11:45 AM, Joseph Orbegoso Pea notifications@github.com wrote:

Check out this nasty problem Chrome 53 introduced (by following a flawed spec), which breaks ReFamous if you want to use opacity on a parent node that contains any children:

https://bugs.chromium.org/p/chromium/issues/detail?id=646993

Please leave a comment there to help persuade them to take back the changes.

You can reproduce the problem with ReFamous by make a parent node, adding a bunch of child nodes with arbitrary X,Y,Z positions, then apply an opacity to the parent node. In the parent node and child node make sure you have a DOMElement so that the result is a nested DOM structure when you inspect element.

What you'll see happen is that the child nodes will be flattened into a plane. This is a really bad design idea from the CSS-transforms specification.

When using mixed mode, then it will cause a nasty bug where the DOMElements are flattened, and the WebGL meshes are not.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.

@trusktr
Copy link
Author

trusktr commented Sep 15, 2016

@Aprender Check out these two Famous 0.7.1 fiddles I made, it shows the problem. The only difference between the two examples is that in the second one I have set opacity to 0.7 on the rootNode:

In the second example, since opacity is set to 0.7, the DOMElement is flattened into the pink background DOMElement, and appears not to move anymore.

Do you mind sending an email to public-fx@w3.org with "[css-transforms]" in the subject, as well as commenting at https://bugs.chromium.org/p/chromium/issues/detail?id=646993, confirming that it is a problem?

@CompSciFutures
Copy link
Owner

Certainly will if it's affecting what I'm doing, just spinning up a new VM now to check. I've just msgd @dmvaldman on Gitter to flag it with him as well.

@CompSciFutures
Copy link
Owner

@truskr, so when you say flatten, it means the transformation matrix is the identity matrix?

@trusktr
Copy link
Author

trusktr commented Sep 15, 2016

@Aprender No, it just means that although the CSS transform properties are applied to the eventual DOM elements that ReFamous is rendering in the DOM, those elements will not obey their transforms, and will be flattened into the scene. I mean, although the CSS transform property is applied by ReFamous, the HTML engine will not transform the elements are we expect while opacity is less than 1.0.

Here's another example: look at this in Chrome 53+, and then look at it in Firefox 47- or Safari 9: https://mightydevs.com

In Chrome 53+ you'll notice that while the letters are fading in, they appear to be flattened onto a single plane. In Firefox and Safari you'll notice that while the letters are fading in they will remain in their cloud formation as expected. In Chrome 53, once the opacity reached 1.0, then the letters will suddenly appear to jump into formation.

The behavior in Firefox and Safari is the correct behavior.

@dmvaldman It would be highly appreciated if you could voice your opinion about this in the above Chrome issue, and in public-fx@w3.org mailing list. From what I can tell, Samsara isn't using the nested-dom strategy yet, so this issue won't affect Samsara.

For reference, here is the public-fx discussion so far: https://lists.w3.org/Archives/Public/public-fx/2016JulSep/subject.html (Look under the topic "[css-transforms] CSS3D breaks with opacity flattening").

@trusktr
Copy link
Author

trusktr commented Sep 15, 2016

Here's a really good example. Try the following in all the browsers, then note how in Chrome 53 the box will be flattened when you press the button: http://codepen.io/jpanter/pen/PGqJOm?editors=1100

When the button is clicked, an opacity animation is added to the box. In Chrome 53, this causes the HTML engine's renderer to flatten the sides of the box into a flat plane. In other browsers, the box will remain a box, and it will become transparent as expected.

@trusktr
Copy link
Author

trusktr commented Sep 15, 2016

@gadicc, would love your input on this too!

@CompSciFutures
Copy link
Owner

Okay I get it - that's just madness.

@dmvaldman
Copy link

dmvaldman commented Sep 15, 2016

Need to think about this a bit more. I welcome the fact that in the flattened version, the layering is correct. Chrome was bad at this before, as it would use DOM order instead of translateZ order post-flattening (like if {overflow:hidden} was applied).

I can see why they did this: defining opacity on a "group" that isn't flattened doesn't really make much sense. The only sense it could make is if you really just meant to define each item of the group with that same opacity. Consider a group that is two planes which intersect at some angle. There are really 2 opacities at play: one when the planes are overlapping, and one where they aren't. There is no opacity for the "group" that is applied once. There is an opacity for each part, which is later composited.

Library authors can solve for this change simply by passing the opacity down to the leaves of their scene graphs. An opacity of .5 for a group of things, just means the opacity for each thing gets multiplied by .5. Everything will then work as intended without changing the API.

@dmvaldman
Copy link

Reading the discussion, the last post (at this time) by Simon is dead on

CSS opacity is defined to be "group opacity", which means rendering everything into a bitmap, and then paint that bitmap with alpha. This gives a different result to painting individual elements with opacity, when those elements overlap.

The CSS transforms spec is conforming to this group opacity behavior, which, in implementations, requires flattening at rendering time.

Authors can work around this by applying opacity on their leaf elements individually, or by using rgba() colors etc.

@trusktr
Copy link
Author

trusktr commented Sep 24, 2016

@dmvaldman

Library authors can solve for this change simply by passing the opacity down to the leaves of their scene graphs.

That's impossible in some cases.

f.e. the following is a hypothetical 3D scene where each item is translated and rotated (using matrix3d) relative to it's parent (for convenience and rather than having to calculate world transforms manually from a virtual tree and having to apply the results to a non-nested DOM structure like in Samsara):

<style>
    ...
    div {
        transform-style: preserve-3d;
    }
    ...
</style>

...
<div style="transform: matrix3d(...)">
    this is rendered content

    <div style="transform: matrix3d(...)">
        this is rendered content

        <div style="transform: matrix3d(...)">
            this is rendered content
        </div>
    </div>
</div>
...

You here, and people in the public-fx thread, propose that in order to make everything transparent we can apply opacity to leaf-node elements directly.

  1. That is impossible in the above example without changing the markup to convert the scene into a flat structure instead of being nested, otherwise the whole object will be flattened as soon as the parent-most div has opacity applied to it.
  2. The individual opacities will be inherited and multiplied anyways, according to new spec.
  3. Someone who has no access to the HTML and can only modify the CSS is out of luck, because this requires a markup change.
  4. After making the markup change (i.e. converting to the non-nested approach for CSS3D), the opacities will no longer be multiplied by the HTML/CSS engine, which will require manual JavaScript in order to do the multiplication.
  5. Having to change the markup changes the structural meaning that is being conveyed, and possibly changes the semantical meaning of the content as far as spiders, crawlers, and accessibility tools are concerned.
  6. Library authors like me, who depend on (and prefer) the nested approach of defining 3D scenes using preserve-3d, and who are defining custom elements that can contain content just like the rendered content in the above examples, will now have to render to a separate root element where all the 3D objects are non-nested, all transforms are multiplied manually rather than by the HTML/CSS engine, and all opacities are multiplied manually rather than by the HTML/CSS engine.
  7. Users of libraries made by those authors will not be able to place content into the custom elements in a reliable way. It destroys CSS selection in the following ways:
    1. If the original content remains (by design of the library) in the custom elements and content is cloned into the new non-nested scene structure, then if the user modifies the original content they will not modify the have successfully modified the content in the rendered non-nested structure. A library could try to solve this by using MutationObserver and copying all changes over, but this does not solve the problem that the user may have a reference to a completely different element than expected so normal JavaScript properties assigned to the reference will not transfer over. CSS selection that relies on the DOM structure that the library user has written will not work.
    2. If the content is moved (by design of the library) into the new non-nested scene, then there at least are only one reference to the user's content, but the user will still have a hard time with selectors.
    3. In either case, the user will have to start using id="" everywhere when not necessary.

All in all, these changes to Opacity just make things really ugly.

@trusktr
Copy link
Author

trusktr commented Sep 24, 2016

@dmvaldman If you have a chance, I'd like to propose that you make an experimental branch of Samsara and try rendering nested DOM structures using preserve-3d (similar to what Famous Engine did, and what I'm doing).

I guess if you did that, and your engine still only renders content on the leaf nodes, then it will be no problem.

But, in my case, my HTML API can be used like the following (similar result as the previous example):

<motor-scene>
    <motor-node position="..." rotation="...">
        this is rendered content

        <motor-node position="..." rotation="...">
            this is rendered content

            <motor-node position="..." rotation="...">
                this is rendered content
            </motor-node>
        </motor-node>
    </motor-node>
</motor-scene>

What then happens is that the position/rotation/etc attributes are composed into a matrix, then applied to the same motor-node elements via transform: matrix3d(). This means that the content (f.e. this is rendered content) are rendered in the scene just like normal HTML.

However, if I want to apply an opacity to the second motor-node, f.e.

<motor-scene>
    <motor-node position="..." rotation="...">
        this is rendered content

        <motor-node position="..." rotation="..." opacity="0.5">
            this is rendered content

            <motor-node position="..." rotation="...">
                this is rendered content
            </motor-node>
        </motor-node>
    </motor-node>
</motor-scene>

then the inner-most motor-node will be suddenly flattened onto the middle motor-node, because my opacity="" attribute gets translated into CSS opacity: 0.5 via the style attribute.

In order to solve the problem, then I have to render to a separate DOM tree that uses non-nested elements. For example, instead of having just motor-node elements in the DOM, the result will be something like this:

<motor-node position="..." rotation="...">
    this is rendered content

    <motor-node position="..." rotation="..." opacity="0.5">
        this is rendered content

        <motor-node position="..." rotation="...">
            this is rendered content
        </motor-node>
    </motor-node>
</motor-node>


```html
<!-- All motor-scene/node elements are inert now, and styled with display:none; -->
<motor-scene>
    <motor-node position="..." rotation="...">
        this is rendered content

        <motor-node position="..." rotation="..." opacity="0.5">
            this is rendered content

            <motor-node position="..." rotation="...">
                this is rendered content
            </motor-node>
        </motor-node>
    </motor-node>
</motor-scene>

<!-- We render to a separate tree using the non-nested approach -->
<div class="motor-scene" style="...">
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */)">
      this is rendered content
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.5">
      this is rendered content
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.5">
      this is rendered content
    </div>
</div>

Now, if opacity worked, we'd be able to also apply opacity to the inner-most motor-node, which would be automatically multiplied by the HTML/CSS engine:

<motor-scene>
    <motor-node position="..." rotation="...">
        this is rendered content

        <motor-node position="..." rotation="..." opacity="0.5">
            this is rendered content

            <motor-node position="..." rotation="..." opacity="0.5">
                this is rendered content
            </motor-node>
        </motor-node>
    </motor-node>
</motor-scene>

But, now that flattening is in place, we have to render the following instead (note the opacity of 0.25 which the library had to calculate manually):

<!-- All motor-scene/node elements are inert now, and styled with display:none; -->
<motor-scene>
    <motor-node position="..." rotation="...">
        this is rendered content

        <motor-node position="..." rotation="..." opacity="0.5">
            this is rendered content

            <motor-node position="..." rotation="..." opacity="0.5">
                this is rendered content
            </motor-node>
        </motor-node>
    </motor-node>
</motor-scene>

<!-- We render to a separate tree using the non-nested approach -->
<div class="motor-scene" style="...">
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */)">
      this is rendered content
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.5">
      this is rendered content
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.25">
      this is rendered content
    </div>
</div>

I hope you can see the problem now.

@trusktr
Copy link
Author

trusktr commented Sep 24, 2016

So, about the user content, it would just work before, and opacity would be multiplied down the tree and the rendered content text would be transparent. But since we don't want opacity to flatten everything, things are tricky as far as managing the user's content. Let me explain.

First, the end library user writes the following markup which is not visible (display:none is set by the library). Note that the motor-node elements contain <h1> elements to be rendered:

<motor-scene>
    <motor-node position="..." rotation="...">
        <h1>this is rendered content</h1>

        <motor-node position="..." rotation="..." opacity="0.5">
            <h1>this is rendered content</h1>

            <motor-node position="..." rotation="..." opacity="0.5">
                <h1>this is rendered content</h1>
            </motor-node>
        </motor-node>
    </motor-node>
</motor-scene>

Then the library outputs the following DOM which will be part of what the user will actually see:

<div class="motor-scene" style="...">
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */)">
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.5">
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.25">
    </div>
</div>

Then the library needs to either (1) move the user's content over, or (2) clone the content over.

If we go with option (1), then the final result will be:

<motor-scene>
    <motor-node position="..." rotation="...">
        <h1>this is rendered content</h1>

        <motor-node position="..." rotation="..." opacity="0.5">
            <h1>this is rendered content</h1>

            <motor-node position="..." rotation="..." opacity="0.5">
                <h1>this is rendered content</h1>
            </motor-node>
        </motor-node>
    </motor-node>
</motor-scene>

<div class="motor-scene" style="...">
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */)">
        <h1>this is rendered content</h1>
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.5">
        <h1>this is rendered content</h1>
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.25">
        <h1>this is rendered content</h1>
    </div>
</div>

If we go with option (2), then the final result will be

<motor-scene>
    <motor-node position="..." rotation="...">
        <motor-node position="..." rotation="..." opacity="0.5">
            <motor-node position="..." rotation="..." opacity="0.5">
            </motor-node>
        </motor-node>
    </motor-node>
</motor-scene>

<div class="motor-scene" style="...">
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */)">
        <h1>this is rendered content</h1>
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.5">
        <h1>this is rendered content</h1>
    </div>
    <div class="motor-node" style="transform: matrix3d(/* Value calculated from multiplying the motor-node tree's values. */); opacity: 0.25">
        <h1>this is rendered content</h1>
    </div>
</div>

In either case, you can see that it will become tricky for the user to target/select the elements they wish to style or to work with.

@trusktr
Copy link
Author

trusktr commented Sep 24, 2016

@dmvaldman Do you see what I mean?

@dmvaldman
Copy link

I'm sorry, I don't see what you mean. Generally speaking, I see the problem: opacity on a group causes the group's transforms to flatten. But I also see a simple solution: when you walk your motor-scene tree, store the opacity and apply them to its children recursively.

It seems option (1) has the output I would expect. I'm not sure what you mean by "moving the user's content over". It seems nothing has changed about the user's content.

@trusktr
Copy link
Author

trusktr commented Sep 25, 2016

But I also see a simple solution: when you walk your motor-scene tree, store the opacity and apply them to its children recursively.

This is the same solution I keep getting from the folks in public-fx, but it doesn't work all the time. See my response at dmvaldman/samsara#51 (comment), where your proposed solution only works when the only things being visibly rendered are leaf-node elements, but that is not always the case; non-leaf elements can be visibly rendered as well.

Now, to explain what I mean about "moving the user's content over":

Let's first assume the changes to opacity in Chrome were not yet published. Let's assume we're in Chrome pre-53, where there is no opacity flattening.

An end user of my library can write some simple HTML/CSS/JS like the following (this is my lib in action): https://jsfiddle.net/trusktr/ymonmo70/5

Now, for the sake of the argument, let's color the box with a red border (imagine the red border is used to denote that the the unit in a strategy game has been selected and is ready to be given commands, or similar): https://jsfiddle.net/trusktr/ymonmo70/9

In that specific example, the red box is the root of a tree in a scene graph; it is a valid 3D object inside a 3D context, and it shows an example of a non-leaf element being rendered. Note, it is a non-leaf element in the scene graph which is visible in the final output (this is in contrast with Samsara that only makes leaf elements visible).

Now, let's make the box AND it's content transparent by applying opacity to the box: https://jsfiddle.net/trusktr/ymonmo70/10

Now you see the motor-node with the class .car is transparent as well as its children, but in Chrome the car will be flattened onto the red box.

We're almost to the "moving the user's content over" part...

If you inspect element in any of these examples, you will see the motor-node elements in the DOM, and you will see the attributes that they have (position, rotation, etc). For each motor-node, my library takes those attributes, creates a transform matrix (using my polyfilled DOMMatrix class), then applies the matrix values to the motor-node's CSS transform via the style attribute. Modifying the position/rotation/etc attributes causes the style attribute to be updated on the same motor-node element.

In essence, the motor-node elements that the end-user defines are the same elements onto which the CSS transforms are applied to.

When the opacity="" attribute is assigned onto a motor-node element, it is simply propagated into the style attribute of that same motor-node. And, as you can see, everything is flattened starting in Chrome 53.

To solve the problem, we have to un-nest the nested elements in order to make them leaf nodes. Do you see what I mean about this part?

To solve the problem in my library while still allowing my end users to write the same exact HTML markup using motor-scene and motor-node elements, my library has to do the following: instead of applying transforms and opacity to those same motor-scene and motor-node elements, the library will instead make those motor-* elements have display:none styling, then my library will need to construct a scene graph next to the motor-scene element using the non-nested approach where all the elements in the 3D context are siblings and never nested.

When you currently inspect the elements in my example, you see there are only motor-* elements, and that is all.

If I implement the non-nested solution that I just described, then what you'll see when you inspect element are those motor-* elements, but then you will also see (as sibling to the motor-scene element) a new set of DOM elements: a div element that contains the 3D context, and inside of it a bunch of non-nested div elements which are the things that will be visibly rendered.

For example, if I implement the non-nested rendering approach, then we'll see something like this in the element inspector:

<!-- This is the original tree that the end library user writes. -->
<motor-scene style="display:none">
  <motor-node position="..." rotation="...">
    <motor-node position="..." rotation="...">
      content
    </motor-node>
  </motor-node>
</motor-scene>

<!-- This is the new non-nested tree that is the visible output. -->
<div class="motor-scene-output">
  <div style="transform: matrix3d(...)"></div>
  <div style="transform: matrix3d(...)">
    content
  </div>
</div>

This solution (creating a second tree that is the visible output and hiding the original tree) is very ugly. Let me explain why.

The user who writes a 3D scene using motor-scene and motor-node elements will expect to see any content inside of the motor-node elements to be rendered in the visible output, which means the that the content needs to exist in the new separate DOM tree, so we need to either:

  1. Move the content over from the motor-scene tree into the visibly rendered non-nested tree, or
  2. clone the content over, in which case the original content remains in the motor-scene tree while cloned content will be placed into the new visibly rendered non-nested tree.

In that example, the content text node had to be cloned from the non-rendered motor-scene tree into the visibly rendered tree.

Do you see where this is going?

Basically, in solving the opacity problem by rendering a second visible tree and making the original markup display:none, the following critical and painful issues will now have to appear in my library:

  1. Having to create a second tree rather than use the original is just ugly.
  2. Having to copy user's content over (either move or clone) is just ugly.
  3. User cannot reliably use CSS selectors anymore, being forced to use IDs when they didn't have to before.
  4. There are now two trees, which means double the memory footprint in the DOM.
  5. Transforms have to be multiplied down the motor-scene tree manually now, instead of the HTML/CSS engine doing it natively, which is extra CPU cost.
  6. Opacity has to be multiplied manually down the motor-scene tree manually now, instead of the HTML/CSS engine doing it natively, which is extra CPU cost.
  7. With my current nested approach where motor-node elements are nested, changing a transform or opacity means modifying the style attribute of a single motor-node element. With the non-nested approach whereby we create a second non-nested tree, changing the transform or opacity of an element means we have to apply transforms and opacity via the style attributes of all descendants. In the nested approach, changing a transform or opacity of a single element means we need to perform a single number-to-string-to-number conversion in order to pass the numerical values into the CSS engine via the style attribute; this results in a single conversion even if the target element has 1000 descendants. In the non-nested approach, if we change the transform or opacity of an item in the scene graph, we must now apply transforms and opacity via the style attributes of the target element as well as all the descendants. This means that instead of a single number-to-string-to-number conversion we will now perform 1000 conversions, which is N times more memory and CPU cost for the conversions where N is the number of descendants.
  8. Having to make a second non-nested tree makes the code more complex.
  9. Having to multiply transforms and opacities manually makes the code more complex if I'm only using DOM.
  10. Having to mirror the user's content from the original tree into the rendered tree makes the code more complex.
  11. Code that is more complex (and ugly in this case) means higher surface area for bugs and higher maintenance cost.

All because of opacity flattening.

The "legacy" behavior already performs the multiplications natively from numerically cached values without massive amounts of number-to-string-to-number conversions, and prevents from having to mirror end-user content, which prevents all the memory and CPU cost I just mentioned.

This is why I feel the change is a regression to 3D programming as far as CSS3D is concerned.

@trusktr
Copy link
Author

trusktr commented Sep 25, 2016

@dmvaldman See the problems, especially with non-leaf rendered content?

@trusktr
Copy link
Author

trusktr commented Apr 7, 2017

I'm still upset about this. This change to CSS forces people to re-arrange their DOM hierarchy to achieve the same effect. When an element has granchildren and is using preserve-3d on all grandchildren, then passing opacity to leafs doesn't work because it leaves non-leaf grandchildren without opacity. The only way to fix this problem as an end-author is to re-arrange the hierarchy, use a non-nested flat DOM, and calculate world transforms manually.

Basically, an entire scene graph needs to converted from nested DOM to flat DOM, which is not simply a change in CSS.

@trusktr
Copy link
Author

trusktr commented Apr 7, 2017

I love how this article beautifully describes the problem: https://css-tricks.com/things-watch-working-css-3d/

@trusktr
Copy link
Author

trusktr commented Apr 7, 2017

@dmvaldman I'm not sure if you understood what I was trying to say up there. In that article I just linked, the solution is to apply the opacity to the leaf DOM node (the cube side). But in that very simple case, each cube has only one level of DOM hierarchy.

Now imagine objects that have multiple levels of DOM (for example 5 levels of grandchild, grand grandchildre, grand grand grand children, etc), where every DOM element in the hierarchy is visibly rendered.

It is now impossible to apply opacity to any of those elements in the hierarchy that aren't leaf nodes, otherwise it flattens sub nodes. The only fix is to stop using nested DOM, and start using the older flat DOM style of rendering (like Samsara does).

@dmvaldman
Copy link

Yes, you are right. If any part of the nested DOM has a visual feature (say a border, or background color), and is not solely used for grouping child nodes, you can no longer isolate and change its opacity without flattening its 3d context.

@trusktr
Copy link
Author

trusktr commented Sep 5, 2021

I'm still annoyed with this change. They could have fixed the layering issue (use Z position instead of element order) while not flattening any 3D elements that are transformed away from the Z=0 plane (3D elements would escape the raster plane).

To this day CSS 3D has been a neglected, unfinished, glitchy API not suitable for production; there is always some problem in some browser.

For example, as of today, load http://lume.io in Firefox on desktop, and notice when you move the mouse to the far left or right edges of the screen some letters glitch out. The very simple cube also glitches the #$%& out. It's rather annoying.

Here's Chrome, which is currently better at CSS 3D:

chrome-is-currently-better

Here's Firefox, where you can see the top of the letter L is missing, and the left side of the cube is rendered behind other sides making it seems as if the cube has an opening:

firefox-css3d-is-garbage

Year after year after year they fix something, and something else breaks. I'm so tired of it. Imagine how much business Unreal or Unity would lose if they constantly broke their 3D API and/or made it do dumb things like opacity flattening.

I'm about to replace the LUME demo with the WebGL version and I am not going to look back at CSS 3D. So much wasted effort in the CSS 3D implementation and in trying to use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants