W3C home > Mailing lists > Public > public-fx@w3.org > July to September 2016

Re: [css-transforms] CSS3D breaks with opacity flattening

From: Rik Cabanier <cabanier@gmail.com>
Date: Mon, 19 Sep 2016 09:33:37 +0100
Message-ID: <CAGN7qDAq9QoxDMpptUO6MawPhygFig5zxv_O-UxWTFmgxpX7=A@mail.gmail.com>
To: "/#!/JoePea" <trusktr@gmail.com>
Cc: "Tab Atkins Jr." <jackalmage@gmail.com>, trchen@chromium.org, Chris Harrelson <chrishtr@google.com>, Simon Fraser <simon.fraser@apple.com>, "public-fx@w3.org" <public-fx@w3.org>
On Mon, Sep 19, 2016 at 1:51 AM, /#!/JoePea <trusktr@gmail.com> wrote:

> Hey all,
> Sorry if my previous replies were rash, but I've collected myself and
> prepared the following response which can hopefully paint a picture of the
> problem as well as propose a solution.
> Let's step outside of the web box for a second, and just imagine: we have
> a 3D engine, and with that engine we have game characters or objects that
> are made of arbitrary number of descendant children (subtrees) in an
> overall scene graph. Imagine these objects are in a game where things fade
> in and out of view and therefore involve transparency.
> I believe we all want the API for doing this to be as easy as possible.
> And, since this is the case, then it makes sense that applying opacity less
> than one to the root node of an object in a scene graph will make the whole
> object transparent without flattening it.
> Take for example, this basic car I just made:
> https://jsfiddle.net/trusktr/ymonmo70/1
=== Challenge ===
> Here's a challenge for you all: make the whole car transparent without
> modifying the markup (it is important not to modify the markup because that
> is what CSS is about). But, if you want to modify the markup, I am open to
> seeing that solution as well (and I already know the non-nested solution
> will work, but will be tedious to migrate to). Keep in mind, this is very
> very easy with "legacy" behavior, and we can make the whole car transparent
> by simply adding the following CSS into the CSS text box:
> ```css
> .car {
>   opacity: 0.5;
> }
> ```
> which results in the following (works in Safari 9 and Firefox 47, broken
> in Chrome 53):
> https://jsfiddle.net/trusktr/ymonmo70/2

Isn't the rendering result of the car wrong? I can see the tires that are
supposed to be occluded by the frame of the car.

> As far as I know, I don't think any of you will have a solution that is as
> simple as simply applying an opacity to the root node of the car. The
> simplest solution I can imagine is applying opacity to all the leaf nodes,
> and that will involve much more work than the addition of a single opacity
> property to a single selector or element.

There is. Apply opacity on the scene.

> With the new behavior, it isn't so simple any more -- *the web's 3D API
> is now **more difficult to use*, which isn't what we want as API
> developers.
> We should aim to make 3D programming more easy, not more difficult!
> Opacity flattening makes 3D programming more difficult because the 3D
> library programmer now has to go and keep track of opacities virtually, for
> every node in the scene (DOM nodes in our case) and then has to traverse
> the scene graph (DOM tree) manually and multiply all opacities manually in
> order to finally apply actual opacities to the leaf-most items (elements)
> in the scene graph.
> Note, even with "legacy" behavior or the new flattening behavior, there is
> currently no way to make rendered non-leaf nodes transparent. In the
> following example, try and make *only* the red box transparent but not
> anything else (i.e. not it's children):
> https://jsfiddle.net/trusktr/ymonmo70/4

you can just apply transparency to the color of the red box.

> With the new behavior, we are limited to making only leaf-nodes of an
> existing scene transparent, and keep in mind we'd like to apply CSS *without
> modifying markup*.
> One of the great things about preserve-3d and nested elements is that
> transformation matrices can be cached, and don't need to be re-calculated
> all the time on the JavaScript side because the HTML engine has them cached
> in the DOM hierarchy. This prevents a TON of string manipulation in
> JavaScript due to converting numbers into strings to pass to the CSS engine
> via style tags (that will be fixed with Typed CSSOM, but we're not sure
> when that's making it into the wild).
> In legacy behavior, the same applies to opacity: the HTML/CSS engine
> caches values and does multiplication automatically, which makes the end
> API easier to use for 3D programming, and *more performant.*
> In order to solve the problem that the new opacity behavior entails, the
> best solution is (unfortunately) to abandon preserve-3d and nested DOM (*losing
> the performance benefits*), then keeping track of the transformation and
> opacity hierarchy manually, multiplying down the tree manually, and finally
> applying *all the values via number-to-string conversion in the style
> attributes, which may involve string parsing, splitting, joining, etc,
> before being actually passed to the style attribute (yikes!)*.
> Reverting to the non-nested approach means that we are forfeiting the
> performance advantages of the nested DOM approach just to achieve what we
> want.
> Note, using the non-nested approach, virtual hierarchies, and manual
> handling of the math allows us to easily make a parent in the hierarchy
> transparent but not its children (which is impossible in the nested
> approach using the `opacity` property).
> === Impact ===
> One argument for the go-ahead to implement this change is that "not many
> people are impacted".
> I'd like to argue that 3D in the web (as far as DOM goes) is already
> fairly difficult compared to code-based APIs (i.e. imperative APIs rather
> than declarative APIs, and not just in the web), and relatively few people
> actually use CSS3D. The vast majority of websites still use only the
> decades-old 2-dimensional technology of the web, and the 3D aspects of
> HTML/CSS are relatively new and difficult to mix with the 2-dimensional
> features. It may be easy to think that many people aren't affected by this,
> but let's take into consideration the ratio of the number of people who use
> opacity in 3D scenes to the number of people who program CSS3D scenes. If
> you take these numbers into consideration, I am willing to bet that the
> number will be a lot higher than the 0.006% mentioned in the blink-dev
> thread
> <https://groups.google.com/a/chromium.org/d/msg/blink-dev/eBIp90_il1o/9q3M0ww2BgAJ>
> .
> Famous was making a an open-source library that depended on the legacy
> behavior, and this change breaks that library. They've gone proprietary,
> and their proprietary stuff probably still depends on the legacy behavior.
> They will now face the chore of re-writing parts of their library to
> convert from nested dom to non-nested dom if they wish for opacity to work
> the same as before.
> I'd like to show you how these changes impact my own library at
> http://infamous.io (which is being renamed and moved to a new domain
> soon).
> My library gives us both a JavaScript API (imperative) and a
> Custom-Element-based HTML API (declarative) for defining 3D scenes. When
> using the JavaScript API, it generates the same elements as when using the
> HTML API. For our purposes, I will show only the HTML API.
> Here is the same car example as above (or, at least similar), made with my
> custom elements:
> https://jsfiddle.net/trusktr/ymonmo70/5
> What is great about this example is that those custom elements are *the
> elements themselves* in the final rendering (look in the element
> inspector and notice that they have `transform: matrix3d()` applied to
> them). This means we can place anything into those elements. For example,
> let's place some text onto the surfaces of the car just for fun:
> https://jsfiddle.net/trusktr/ymonmo70/6
> Now, if we apply opacity, the whole car will be flattened in Chrome 53
> (compare it to Safari 9) because we've applied opacity onto the `.car`
> element element:
> https://jsfiddle.net/trusktr/ymonmo70/7
> I believe I have something really nice in the works, and plan to add WebGL
> soon, but now it is fundamentally broken: in order to fix the rendering,
> but keep my same nested markup, I will have to render new elements into a
> flat structure that sits next to my <motor-scene> elements. This means that
> for each motor-node element I will create a reciprocal element in the
> actual rendering context, while my custom elements will not actually be
> rendered (they will be display:none). In other words, although I'd like for
> my API to be nested and using preserve-3d, I will have to render *completely
> separate elements* next to my custom elements in order to solve the
> opacity problem that is on my hands now.
> But, there are some problems with this! Remember that we were able to
> place content like text inside the custom elements, and it worked fine
> because the custom elements are part of the actual rendered scene?
> Well, now that won't be the case any more. Now I have the following
> problems to deal with:
>    1. I have to convert my code so it renders non-nested elements next to
>    my custom elements (or maybe inside a ShadowDOM root).
>    2. I will have to implement transformation caching just for DOM
>    rendering, and can no longer take advantage of the HTML engine's transform
>    caching.
>    3. Same for opacity caching down the scene graph, I will need to
>    implement it rather than let the HTML engine handle it.
>    4. Lastly, but most problematically, I have to find a way to clone or
>    copy the user's content from inside my custom elements into the new
>    non-nested elements. This will have detrimental effects on the user's
>    ability to target and select those elements for styling, as they will no
>    longer appear in their original places. Users will need to use IDs all over
>    the place when they probably shouldn't, and will likely mess up a bunch of
>    times before discovering the ID workaround. This may also have an impact on
>    SEO, but I haven't looked into that much yet.
> The first three points aren't as bad as the fourth, as I'll have to
> implement the first three points when I add WebGL anyways. But, the fourth
> point is really bad! The new behavior is an extreme pain because of point
> number four.
> I hope you can see how, from my perspective as a library author writing on
> top of HTML/CSS3D, this is a very unwelcoming change if opacity is to have
> any meaning in my 3D scenes.
> There aren't many people doing what I'm doing. There are only three
> libraries out there that are doing what I am doing (that I know of):
>    - Three.js <http://threejs.org> - Three.js uses the non-nested
>    approach, so does not face the opacity problem. People who use Three.js'
>    CSS3DRenderer know what they are working with from the start, and they
>    won't possibly get confused in targeting their elements for styling,
>    because their elements will not be transplanted like with my library. But
>    note, Three.js will not be able to update to the nested-approach due to
>    this problem, and therefore won't be able to have the performance benefits
>    of the nested approach.
>    - Famo.us <http://deprecated.famous.org> (0.3 and below) - Like
>    Three.js, uses non-nested approach, and is worry-free like Three.js.
>    - Famo.us Engine <http://github.com/famous/engine> (0.5 and above) -
>    Uses the nested approach, and is now broken just like my library.
>    - Samsara <http://samsarajs.org> - Uses the non-nested approach, so no
>    problems there.
>    - Infamous <http://infamous.io> - My library, which uses the nested
>    approach. Problem!
> In sincerity, opacity flattening is a breaking change that shouldn't have
> been implemented by any browsers without a full solution for "legacy" code
> with 3D in mind.
> === What We Need ===
> What we need are possibly at least three forms of opacity:
>    1. 3-dimensional, like the legacy behavior where things don't get
>    flattened and opacities are multiplied down the hierarchy.
>    2. Parent-only (opacity on a single element), where nothing is
>    flattened, and opacity is applied only to the target element and not its
>    children. Note, there is currently no way to do this in the nested
>    approach, only in the non-nested approach with virtual hierarchies. In
>    "legacy" behavior, we can only apply opacity to an entire subtree of a 3D
>    context, not just to a parent element, so that's either all or nothing.
>    3. Flattening, which is the new behavior, though I fail to see why
>    anyone applying opacity to a 3D object desires for the object to be
>    suddenly flat, as that doesn't make much sense and is unintuitive, and
>    means the API is more difficult to use that what we'd like.
> Note, with "legacy" behavior, *it is already possible to create a new 3D
> context and apply opacity to it*, so this new behavior isn't actually
> needed (from a functional perspective), because we can currently achieve
> the same behavior with the "legacy" implementation. The new behavior does
> two things:
>    1. it eliminates the desired 3D behavior in nested scenes that use
>    preserve-3d
>    2. it adds a second method of opacifying an entire 3D scene which *we
>    can already do*.
> Here is an example of just that using "legacy" techniques, where I simply
> add the style `motor-scene {opacity: 0.5}` to the CSS, which opacifies the
> 3D context:
> https://jsfiddle.net/trusktr/ymonmo70/8
> We all agree that the spec (whatever it says) should be well defined so
> that it is clear what browsers should implement. I also believe that the
> legacy behavior is much more desirable for 3D programmers than is the
> flattening of objects (legacy-like behavior just needs to be clearly
> spec'd).
> === Possible Solution ===
> Maybe we can improve the spec (before making breaking changes, and browser
> developers, you all roll back to "legacy" implementation ASAP) so that the
> spec can appease all three opacity styles.
> This could be achieved with something like a new CSS property called
> `opacity-style`, f.e.
>    - `opacity-style: 3d` which is similar to the legacy behavior and is
>    great for 3D scenes, and does not flatten anything. Opacity is multiplied
>    all the way down the tree with other same-style opacities and stops at
>    elements that have a different opacity-style (`flat` or `single`).
>    - `opacity-style: single` which does not flatten anything and only
>    applies opacity to the target element but not its children
>    - `opacity-style: flat` which is like the new behavior that Chrome 53
>    just introduced, things are flattened and a new 3D context is made. I still
>    fail to see how this is desirable. *Assuming we can apply an opacity
>    to a 3D object to make it transparent and then having it become flat like
>    paper is simply not intuitive.*
> The default value can be `opacity-style: single`, which I think is the
> least impacting of the three. We can then see what 3D programmers end up
> using more often (I'd like to bet that `opacity-style: flat` will be the
> least used). Or, perhaps the default for a 3D object can be `single`, and
> `flat` for non-3D objects.
> What are your thoughts on something like this, `opacity-style`? Because,
> as it stands, opacity in the 3D web isn't very workable now in nested
> scenes (it is much more workable with the legacy behavior).
> */#!/*JoePea
> On Thu, Sep 15, 2016 at 11:16 AM, Tab Atkins Jr. <jackalmage@gmail.com>
> wrote:
>> On Thu, Sep 15, 2016 at 10:02 AM, Rik Cabanier <cabanier@gmail.com>
>> wrote:
>> > On Wed, Sep 14, 2016 at 8:44 PM, /#!/JoePea <trusktr@gmail.com> wrote:
>> >> Here's an example using Famous Engine (http://github.com/famous/engine
>> ):
>> >>
>> >> First, with opacity at the default value of 1.0, the "Famous Code" logo
>> >> moves back and forth and rotates:
>> >> http://jsfiddle.net/trusktr/spauv8fs/5
>> >>
>> >> With opacity reduced, it breaks in Chrome 53:
>> >> http://jsfiddle.net/trusktr/spauv8fs/6
>> >>
>> >> Famous Engine has the ability to mix WebGL with DOM, and this includes
>> >> opacity. This new behavior causes the DOM elements not to move in 3D
>> space
>> >> the WebGL meshes.
>> >>
>> >> First, here's a demo, with opacity at 1.0:
>> >> http://jsfiddle.net/trusktr/spauv8fs/7
>> >>
>> >> The, with opacity at 0.7:
>> >> http://jsfiddle.net/trusktr/spauv8fs/8
>> >>
>> >> What you are supposed to see is a DOM element and a WebGL Mesh that
>> both
>> >> seem to intersect with the pink DOM-based background (the
>> implementation is
>> >> not perfect yet...). In the second fiddle (spauv8fs/8) the "Famous
>> Code"
>> >> logo appears not to move any more while the WebGL mesh continues to
>> move.
>> >>
>> >> There is a bug in the WebGL renderer which I believe may be due to
>> changes
>> >> in WebGL, but the WebGL part of those examples is supposed to be
>> transparent
>> >> as well.
>> >>
>> >> I myself am working on a new implementation of DOM + WebGL, and it
>> allows
>> >> application of opacity. This change in Chrome 53 completely breaks how
>> that
>> >> is supposed to work. I don't have an online demo yet...
>> >>
>> >> I would say let's definitely consider undoing the changes to the spec
>> so
>> >> that flattening does not happen when applying an opacity to 3D DOM
>> elements.
>> >> The reasoning is not just to prevent breaking apps, but because the new
>> >> behavior simply doesn't make sense as far as 3D goes.
>> >
>> >
>> > I don't understand how you come to that conclusion.
>> > The new behavior seems more logical since it applies the opacity to
>> element
>> > that has the property applied. The old implementation distributed the
>> value
>> > to its children which is counter to any other CSS value.
>> > Are you proposing that we also do this for filters, blending, backdrop
>> > blurring and other effects, or is opacity special in some way,
>> >
>> > As Simon stated, if you want the old behavior, just add a selector to
>> your
>> > opacity parameter so it's applied to the children.
>> Yes. This is not a spec bug, it's a natural and unavoidable
>> consequence of doing a "group effect", which opacity, filters, and a
>> few other effects are.  These types of effects require the group to be
>> rendered as a unit, then have the effect applied; in 3d space, this
>> has the effect of flattening them. (If you didn't flatten, then other
>> items could get between the individual pieces in 3d order, and there's
>> no consistent or sensible way to render that. This is identical to how
>> z-index is "flattened" by opacity and other group effects, so you
>> can't sandwich elements elsewhere in the page between the elements in
>> the group.)
>> If you want to sandwich things, you need to push the effect further
>> down into the leaves, so it doesn't group as many things together.
>> This lets you do more re-ordering, but has a different visual effect -
>> if one item occludes another in a group, when they're made transparent
>> they still occlude; if they're made individually transparent, you'll
>> be able to see the second item behind the first.  Similar differences
>> exist for other group effects - if you're doing a gaussian blur,
>> blurring two boxes as a group can look quite different than blurring
>> them individually.
>> As Simon said, Chrome 52 and earlier Safari are simply buggy and not
>> "grouping" things correctly; they're instead automatically pushing the
>> opacity down further toward the leaves when 3d is involved.  If you
>> want the same effect, just do so manually, as Rik recommends.
>> (We had an almost identical request in the SVG Working Group a few
>> days ago. I posted
>> https://github.com/w3c/svgwg/issues/264#issuecomment-246750601 as an
>> extended explanation of what's happening.)
>> ~TJ
Received on Monday, 19 September 2016 08:34:08 UTC

This archive was generated by hypermail 2.4.0 : Friday, 17 January 2020 19:49:57 UTC