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-variables] When does substitution occur, before or after animation interopolation? #411

Closed
gregwhitworth opened this issue Aug 17, 2016 · 13 comments
Assignees

Comments

@gregwhitworth
Copy link
Contributor

I think the spec could be a little bit more clear on when substitution actually occurs for custom properties. We know that they happen during computed value time, but this doesn't get into the minutia of whether or not this occurs before the animation interpolation or after.

If you take the following example, what should happen:
http://jsbin.com/busapezohu/1/edit?html,css,output

Chrome: No background is painted for either div
FF: Second div background is transitioned

Currently the spec states the following:

Notably, they can even be transitioned or animated, but since the UA has no way to interpret their contents, they always use the "flips at 50%" behavior that is used for any other pair of values that can’t be intelligently interpolated. However, any custom property used in a @Keyframes rule becomes animation-tainted, which affects how it is treated when referred to via the var() function in an animation property.

This seems to state that they should work, but that it may not be possible to correctly interpolate a custom property (as seen in the test animation since it's merely an ident at that point). So I would suggest that what Firefox is doing is the correct outcome, that you do substitution first and then any animation interpolation once we know the value and the property it is applied to is blue we can correctly interpolate to that of blue (as seen in the regular testcase).

This may even want to be taken over to the cascade specification where we get more specific about when certain things happen during various stages, in this case Computed Value time.

cc: @shans as this is what I was pinging you about :)

@gregwhitworth gregwhitworth added the css-variables-1 Current Work label Aug 17, 2016
@gregwhitworth
Copy link
Contributor Author

@tabatkins @shans

I've looked at the Houdini minutes and there was a breakout session regarding this but I can't seem to find minutes. At any rate, basically I'm trying to understand what the preferred expectation is regarding the following code:

div {
  --foo: green;
  background: var(--foo);
  animation: test infinite 1s;
  width: 100px;
  height: 100px;
}

@keyframes test {
  from {
    --foo: blue;
  }
  to {
    --foo: yellow;
  }
}

Should the div be:

  • green (what FF/Chrome currently does)
  • blue

@jyc
Copy link

jyc commented Aug 19, 2016

Hi @gregwhitworth,

I agree the first & third divs should flip at 50% between red and blue, from animation: test 1s infinite on the declaration block for div. As you quoted, 'Notably, they can even be transitioned or animated, but since the UA has no way to interpret their contents, they always use the "flips at 50%" behavior that is used for any other pair of values that can’t be intelligently interpolated'. It looks like this behavior is currently not implemented by Firefox or Chrome, however.

With the patches to implement Properties & Values on Firefox, we do get the 50% flipping behavior, as required by the Properties & Values spec.

Firefox's behavior looks correct for the second div. The .regular declaration block is more specific and takes precedence, so we use the regular animation. In the from keyframe, we take the value from .regular (red), and in to we substitute the --foo declaration there in background. The result is that we have background: red in the first keyframe and background: yellow in the second keyframe, and background is an animatable property & color is interpolable, so we interpolate between red and yellow.

@tabatkins
Copy link
Member

@gregwhitworth My expectation is that the div's background flips from blue to yellow. (If that didn't work, the whole "animation-tainted" concept wouldn't be worth much.) The spec does need a little bit more detail there, but the general principle is supposed to be that animations run before we do variable substitution.

@shans
Copy link
Contributor

shans commented Aug 22, 2016

I agree with Tab - the animation's variable should be used in computation of the style of the div. The fact that --foo is animation-tainted doesn't matter (I think Tab's pointing out we wouldn't need to bother with animation tainting if animation variables couldn't leak into the environment).

I guess what's important is that the animation's variable "wins out" over the one defined in the div rule, but this is just normal - e.g. imagine if you set background directly in the animation instead.

@gregwhitworth
Copy link
Contributor Author

@shans @tabatkins Thanks, this is what we were expecting and kind of shocked that currently no one implements it this way. Does Chrome plan to address this, I would assume so since you're working on the Houdini Custom Props which was primarily created for extending the animation capabilities that are somewhat limited due to needing to substitute post animation.

@gregwhitworth
Copy link
Contributor Author

One last comment, I highly recommend adding it the example I provided with the expected outcome and why. While animation may be considered part of the "cascade" in the Cascade spec it is actually done on the computed results of the cascade. I think this example shows implementers what the expected behavior is, and also informs them that substitution should occur after animation which is currently not stated anywhere, at least not in plain language (it may be implied).

@gregwhitworth
Copy link
Contributor Author

Going to reopen until the spec update is done, assigning to @tabatkins since he's the editor.

@gregwhitworth
Copy link
Contributor Author

@tabatkins @shans and myself spoke about animating custom properties and how many substitutions you need in order to support use cases the authors would expect to work. I recommend adding the following copy as a note for an example implementation to ensure the spirit of the specification is followed by all implementations (which when we began this implementation no UA supports this, so it would be good to ensure it's in there).

In order to be able to make the use cases work, here is what an example implementation would look like:

  1. Apply cascaded values
  2. Detect & Construct animations
  3. Substitute into keyframes
  4. Detect & construct transitions
  5. Apply computed values from transitions and animations
  6. Substitute into the cascaded values

While this will help solve some of the gaps and interop issues we found while implementing custom properties, I think we should specify these steps normatively in somewhere (possibly the cascading specification) so that we can reference them and better understand the potential use case and perf impacts with future feature discussions (eg: @apply).

@gregwhitworth
Copy link
Contributor Author

gregwhitworth commented Feb 9, 2017

@tabatkins any thoughts on this? And @fantasai any thoughts on normalizing these steps in the cascade spec so in the future when discussing these types of features and people use the terms such as "an at-apply" point or a "substitution" point; we can better reason about them?

@gregwhitworth
Copy link
Contributor Author

Hey @tabatkins do you want me to do a PR on this?

@svgeesus
Copy link
Contributor

svgeesus commented Sep 8, 2017

Going to reopen until the spec update is done, assigning to @tabatkins since he's the editor.

Hey @tabatkins do you want me to do a PR on this?

@gregwhitworth a PR would be great. Also, a test case on WPT that tests the change (or a link to a test that already tests this).

@tabatkins
Copy link
Member

Okay so I've been researching this to finally get a proper answer down in the spec, and the answer seems, uh, complicated.

Firefox, as far as I can tell, takes an easy route: custom properties defined directly on the element are visible to animations (can be used as var() inside of @Keyframes), but custom properties defined in @Keyframes aren't visible to properties directly on the element. (They are visible to other rules in the same keyframe, but not cross-keyframe.)

Chrome does something more complicated that I'm still puzzling out. It has all of Firefox's cross-context behavior, plus custom properties defined in @Keyframes are visible to the element itself. They appear to be visible cross-animations, too; that is, a custom property animated by one @Keyframes can be used by a different @Keyframes. Exactly how it determines which properties are visible to what, when, looks complex; I think animation order matters; keyframe temporal ordering might matter, but I haven't gotten a testcase together to demonstrate it for sure yet.

So I'm gonna do some more exploratory work on this and possibly bug Chrome/WK implementors about details before I commit to something to bring to the group.

@tabatkins
Copy link
Member

tldr: As far as I can tell, browsers are currently treated variables in animations differently than other computed-value dependencies, and they should stop. The correct behavior was resolved back in 2014, but hasn't been put into the Animations spec. Once it is editted in, variables behavior should naturally fall out, and we don't need any special text for this spec.


When figuring out how custom properties work with animations,
there are (at least?) four relevant contexts we need to worry about:

.foo { 
	/* A */ 
	animation: test infinite 1s, test2 infinite 1s;
}
@keyframes test {
	from { /* B */ }
	to { /* C */ }
}
@keyframes test2 {
	from { /* D */ }
}

That is:

  • (a) styles applied directly to an element
  • (b) styles appearing in a keyframe
  • (c) styles appearing in a different keyframe in the same @Keyframes
  • (d) styles appearing ina different @Keyframes entirely

So there's a 4x4 matrix (with many cells being dupes)
of locations where a custom property can occur,
and where a var() can show up referring to the custom property,
and we need to figure out in which situations there is data flow.
For example, if a custom property is defined directly on an element (a),
is it visible to var()s in a keyframe (b)?
What about vice versa?

Tests for each situation (named for custom prop location and var() location, like a/b for the example above):

Chrome ✔✘, Firefox ✔✘

  • a/a: normal animations behavior, not tested

  • a/b, a/c, a/d: Chrome ✔, Firefox ✔

  • b/a, c/a, d/a: Chrome ✔, Firefox ✘

  • b/b, c/c, d/d: Chrome ✔, Firefox ✔

  • b/c, c/b: Chrome ✘, Firefox ✘

    Behavior is still slightly different here,
    seemingly due to different timing of the "try to resolve the var() reference" action.
    It looks like Chrome resolves the animation first then resolves var()s,
    so the color/bg is just a constant var() ref,
    and the custom prop they're referring to is discretely animating between valid and invalid.
    Firefox instead resolves the var()s before resolving the animation,
    so the color/bg smoothly animates from a color (based on the custom prop) to a color (initial value due to invalid custom prop).

    This is maybe more obvious in http://software.hixie.ch/utilities/js/live-dom-viewer/saved/9529,
    where Chrome flashes between two states
    while Firefox smoothly animates between the two states.

  • b/d, d/b, c/d, d/c: Chrome ✔, Firefox ✘

    Just in case animation order matters, I tested both "custom prop in animation preceding the var() animation" and vice-versa;
    results were same for both.

Firefox's overall rule seems to be that custom props defined on the element are visible to animations,
and custom props defined in a keyframe are visible to that keyframe only
(overriding any custom props coming from the element itself, per usual for animation-origin declarations),
but there is no other data flow.
Chrome's is more complicated.

I think both can be explained in the same way, with a single change in var() substitution timing:
both browsers treat custom properties the same as normal properties
wrt animation styles and element styles mixing,
but Firefox substitutes var()s ASAP
(on elements, before cascading animations;
within animation styles, before interpolation),
while Chrome substitutes var()s as late as possible
(on elements, var()s don't resolve until after animations are cascaded;
within animation styles, var()s stay unresolved until after interpolation).

Reviewing some old animation issues
and doing some tests of other computed-value dependencies between animations,
afaict both browsers are giving variables different behavior than things like em units,
and both are violating the WG agreement to use option G.β
to resolve computed-value dependencies in the presence of animations.

In particular:

  • Firefox's timing wrt resolving var()s in static styles before animations are applied
    means that you can't take a custom property on an element
    and animate it.
    (Contrary to the current behavior where an em in static styles
    is affected by an animated font-size.)

  • Chrome's timing wrt resolvings var()s in animated styles after interpolation
    means you can't extract common values from properties
    and then animate the properties smoothly.


So I think the behavior is actually just something that should fall out of the Animations spec
once someone (sigh, probably me) edits in the G.β option properly.

I initially thought it might still require some special text,
since animation properties could depend on variables
and thus need an early compute pass over the custom props
(which later gets overridden once animations are applied),
but that's actually true of general properties now,
since the animation properties might depend on font-size
if you, say, specify animation-duration using em and unit algebra
to get the units to land on <time> in the end.

The general behavior will likely be, then,
that animation properties get computed in an initial pass,
along with a theoretical computing of any properties
they happen to grab a dependency to
(custom props, font-size, etc).
Then you set up animations
and re-resolve dependencies as specified by G.β,
potentially getting different values than originally computed.

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

No branches or pull requests

6 participants