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
Comments
Oh dear. Thanks for the heads up.
|
@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? |
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. |
@truskr, so when you say flatten, it means the transformation matrix is the identity matrix? |
@Aprender No, it just means that although the CSS 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"). |
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. |
@gadicc, would love your input on this too! |
Okay I get it - that's just madness. |
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 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. |
Reading the discussion, the last post (at this time) by Simon is dead on
|
That's impossible in some cases. f.e. the following is a hypothetical 3D scene where each item is translated and rotated (using <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.
All in all, these changes to Opacity just make things really ugly. |
@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 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 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 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. |
So, about the user content, it would just work before, and opacity would be multiplied down the tree and the First, the end library user writes the following markup which is not visible ( <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. |
@dmvaldman Do you see what I mean? |
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 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. |
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 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 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 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 When you currently inspect the elements in my example, you see there are only If I implement the non-nested solution that I just described, then what you'll see when you inspect element are those 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
In that example, the Do you see where this is going? Basically, in solving the opacity problem by rendering a second visible tree and making the original markup
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. |
@dmvaldman See the problems, especially with non-leaf rendered content? |
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 Basically, an entire scene graph needs to converted from nested DOM to flat DOM, which is not simply a change in CSS. |
I love how this article beautifully describes the problem: https://css-tricks.com/things-watch-working-css-3d/ |
@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). |
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. |
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: Here's Firefox, where you can see the top of the letter 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. |
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.
The text was updated successfully, but these errors were encountered: