-
Notifications
You must be signed in to change notification settings - Fork 708
[css-animations] Resolving dependencies in keyframes #5125
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
Comments
This is my fault because I went to make the spec edits but didn't want to make them without adding WPT. When I went to add WPT I realized how poor the existing WPT were and started rewriting them but didn't finish before I left Mozilla. (Also now that I've lost access to my old Mozilla mail I can't find the different mails where I summarized the discussion so far 😅) A couple of starter links though:
As for Firefox's behavior, it's definitely a known problem. We have put off fixing it a number of times thinking that we would fix it properly when we implement the Properties and Values API. Unfortunately we've had two incomplete attempts at that. At this stage, since I don't have experience with Properties and Values and am less involved in implementation these days I suspect @emilio or @hiikezoe or @BorisChiou might be better able to comment.
I'm sorry, I haven't given your proposal proper thought (Friday afternoon here after a tough week) but one comment here is that I suspect we need to stick to returning computed keyframes from The reason is that it's simply not possible to represent specified CSS keyframes accurately using Javascript objects. There are all sorts of edge cases (e.g. declaration order matters in CSS, but not in JS; CSS can have duplicate declarations but JS can't) so you end up needing to expand shorthands etc. in order to correctly represent the the CSS markup -- and that in turn means you end up needing to compute values (since you need to know how variables are going to expand). I really tried hard to preserve specified values in the CSS animation keyframes from |
Thank you @birtles! This means that current state of affairs is that G-β is supposed to be the correct behavior? If I'm not mistaken, this is approximately the same as option 3 here. (Right?)
But "just" using computed values doesn't work either, does it? Say there's an animation:
And at the same time, there's a transition on
Because, at the time getKeyframes is called (and at any other time really), there is only one computed value for If the above is what we want, then we're not substituting "the" computed value, but some value that's equal to the would-be computed value at the time indicated by the keyframe. |
Right.
Right, I think we need to distinguish between what the browser internally stores and what So it's fine to internally retain the
No, you'd get a different object depending on when you called {
offset: 0,
width: '10px'
},
{
offset: 1,
width: '20px',
} and at the end you'd get: {
offset: 0,
width: '20px'
},
{
offset: 1,
width: '40px',
} That means that round-tripping the result of Somewhere I wrote a long comment about some of the edge cases that make this expansion necessary but I can't find it now. I think though it's particularly cases like: :root {
--two-values: 10em 20em;
}
body {
font-size: 10px;
}
@keyframes a {
to {
margin: var(--two-values);
margin-right: 30em;
margin-right: 40em;
}
to {
margin-left: 50em;
margin-inline: 60em;
margin-inline-start: 70em;
}
} The full-fidelity representation of that using Web animations keyframe objects might be something like: {
offset: 1,
margin: 'var(--two-values)',
marginInline: '60em',
marginInlineStart: '70em'
} but when the writing-mode changes it might be: {
offset: 1,
margin: 'var(--two-values)',
marginInline: '60em',
marginInlineStart: '70em',
marginRight: '40em',
marginLeft: '50em'
} (And we can't unconditionally include Simply trying to keep track of which properties clobber which is difficult in light of overlapping keyframes and shorthands and logical properties so it's easiest if you can expand to physical longhand properties. But then that becomes complicated because until you expand variables, you don't know where properties will get their values from. Hence why we ended up just expanding everything to physical longhand properties with computed values when generating keyframes from |
Thank you again @birtles for that detailed answer.
OK, then it is actually "the" computed value, so it's consistent in that regard. And if we do keep interpolating in specified values but compute the keyframe at However, the object we'd return seems like it has nothing to do with the actual animation. The keyframes you get don't actually represent the things we're interpolating between, but instead you get "random" keyframes depending on the current state of other interpolations. This seems not useful, and misleading. It's both losing and adding information. It would IMO even be preferable to spec that Ideally we would change how keyframes are represented in JS if we're going for an approach that interpolates in specified value space (e.g. G-β). Otherwise, even if we did manage to stack enough special rules on top of each other to deal with all the difficulties, we'll just keep running into the problem each time CSS/the cascade/interaction-between-properties changes in the future. Zooming out a bit again, I wonder whether G-β really is very important to the author. It seems to me that reasonable (enough) behavior both for the actual interpolation and @birtles: How likely is it that we can change the (spec'd/resolved) behavior of:
|
I think that might be overstating things a bit. The object returned represents the actual values we are interpolating between at that point. Interpolation happens in computed value space so we have to convert to computed values to interpolate and the object returned accurately represents that. For some animations, other (typically independent) effects (e.g. from JS or other animations) may cause those interpolation endpoints to change on the next frame, but that doesn't make these values "random" any more than the values from To give some concrete use cases for these values (and these are literally the first two use cases that came to mind):
In both cases, the fact we were able to get the keyframes is of great benefit as otherwise these effect would be impossible without manually fetching and cascading all the
Given that we don't have good interop here, this seems possible depending on the scope of the change.
To the extent that these align with what we pass to
Given that this has only shipped recently, it might be possible but, depending on the scope, would probably need some use counters / telemetry to be confident we are not breaking content. |
I thought about some other alternatives like introducing |
I admit I didn't think about it that way. In isolation that's fair enough.
Didn't think about it that way either. I was thinking interpolation happens in specified value space, e.g. interpolation from
If you do And if you do:
And roundtrip that with
Isn't that basically what this is? 🙂 |
@birtles In the interest of trying to get this issue to "converge" on something, let's assume that we want to continue with G-β, and that we also want to return "computed keyframes" from But first I need to clarify the behavior:
Some implications of G-β + the above are (for example): (@birtles: please review and confirm the examples).
At
At
This is the basically the same thing as
The interpolated What I think we should do now:
Another possible other way of explaining it is that the animation
Where the params are resolved to absolute values before interpolation occurs. When it comes to |
At a spec level, we only define interpolation between computed values so we have to convert the endpoints before interpolating.
Yes, I was thinking about having a However, I think that ship has sailed. We already have a
Yes, that's right. There is an ordering dependency there. I believe @alancutter worked on this in the context of custom properties in Blink and said it was quite hard. Those other examples all match my understanding of the expected behavior.
Thank you! 🤩
Sounds good.
Yes, that's right. Obviously this is in the context of CSS animations where the keyframes have not been overridden by script. For script-generated Web Animations or
Yes, that would be very helpful. I expect Firefox will fail a lot of them because we haven't implemented the ordering dependencies or registered custom properties.
I'm not aware of any. Thank you! |
This came up (again) during @kevers-google's in-progress implementation of getKeyframes in Chrome. Since getKeyframes directly exposes computed keyframes, it forces us to deal with how/when dependencies in the keyframe are resolved in more detail.
We should in my opinion take a "broad" approach of thinking about all dependencies, and not specify detailed behavior per case. For example,
1em
has a dependency onfont-size
, andvar(--x)
has a dependency on--x
. In terms of dependencies,1em
andvar(--x)
are very similar, so for consistency, these cases should be treated the same way.Browsers generally don't agree on how interpolation actually happens when complicated dependencies are involved. I won't enumerate all differences, but instead highlight this (well-known) example:
The computed value of
width
ondiv
for the different browsers:150px
. (1)50px
. (2)45px
. (3)@kevers-google, @flackr and myself have had several discussion about this recently, and three different ways of dealing with it surfaced in those discussions:
1. Resolving against the pre-animated style
In this model, the
em
units would resolve against the would-be computed value offont-size
without animation effects, i.e.10px
in the first example. The interpolation forwidth
would take place in absolute values, between100px
and200px
. Forvar()
references, they would behave similarly: the value would be substituted using the pre-animated computed value of the referenced custom property.I think this solution is a nice one in terms of technical complexity, but I'm not sure if it matches author expectation.
This appears to be what Firefox is doing (for
em
units).2. Resolving against local values in the keyframe
In this approach, each keyframe is basically a list of declarations that's added to the cascade, and we derive computed values from that. In approximated spec language:
In the first example,
em
units would then resolve against thefont-size
specified locally in the keyframe (if present). The interpolation ofwidth
would be between20px
and80px
.This approach would match my initial expectation as an author, I think. But not sure how practical it would be to implement this. Although Safari has the apparent behavior of this model already, at least for the example I gave.
Note that an interesting side-effect of this suggestion is that !important declarations from the base style can end up in the computed keyframes.
3. Resolving against the (animated) computed value
This is what Chrome is currently doing for
em
units (but not forvar()
). In the example,width
interpolates inem
-space from10em
to20em
, at the same time asfont-size
interpolates from2px
to4px
. Theem
unit is resolved computed-value time (as apposed to before the effect value is added to the cascade), against the animatedfont-size
at that time. So Chrome produces45px
in the first example via15em * 3px = 45px
.For
var()
it would mean that, for this example:The animation would add a value to the cascade that can be illustrated by:
calc(var(--a) + t * (var(--b) - var(--a)))
. In other words, like forem
resolution, thevar()
resolution would be delayed until computed-value time. (Again, Chrome does not currently do this, but it would be the consistent thing to do in this model).Not sure what I think about this behavior. On the one hand it produces unnecessarily complex animation behavior, but on the other hand it means the concept of "computed values in keyframes" can be mostly avoided, which has a certain simplicity.
Note that getKeyframes in this model should in my opinion not return the keyframes as if "all property values are replaced with their computed values" as the spec currently suggest, as this would misrepresent the endpoints of the interpolation.
So at this point I was hoping for some feedback regarding which approach is better, whether or not the author really cares about this at all, how compatible each solution is w.r.t. web-animations, and whether or not there are other approaches that should be considered. Thanks.
cc @birtles @dbaron
PS: I realize this topic has been discussed many times before, and I apologize for not digging up all prior discussion in advance. (Please add links). At the same time, I hope we can avoid treating prior resolutions as automatically holy and immutable, since they apparently failed (so far) to effectuate any real consensus in browser behavior.
The text was updated successfully, but these errors were encountered: