-
Notifications
You must be signed in to change notification settings - Fork 711
[web-animations-1] Additive transform animations easily invoke undesirable matrix interpolation #2204
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
@alancutter welcome back! I seem to remember there being pretty good reasons for using the order we have although I don't recall what they are just now. Is it not possible to fix this by defining more intuitive interpolation for |
Thanks. (: I don't think the interpolation behaviour can be tweaked to deal with Perhaps the reasons for doing composition before interpolation is to deal with neutral keyframes and add+replace keyframes. I think a new operation will need to be added to handle that case, one that takes an interpolable CSS value and produces a "noop" equivalent. Let's say we have the animation
Pseudocode:
|
Sorry, does this mean that the element's un-animated computed style is In any case, I'm pretty sure you're right. And I think that's why initially the neutral value for composition was something that other specs were going to have to define for each type. I'm starting to wonder if the reason we went with this particular order was related to handling |
The second keyframe is a replace keyframe so the underlying |
I'm not sure I understand why we need the neutral value. I think as long as each animation is interpolated before being composited we should get the expected behavior - similar to if you put the animations on different elements, e.g. https://jsfiddle.net/flackr/1qj34byw/54/. I suppose for neutral keyframes this may prevent you from matching the shape of the underlying style but I'm not sure how often this is useful rather than just unexpected. |
I thought it was the other way around? If you interpolate first you do need the neutral value, right? Since, if you're missing the from/to keyframe, you need to interpolate with something. When you composite that something with the underlying value it should produce the underlying value. If we composite first I don't think we need the neutral value concept since the missing from/to keyframe value just becomes the underlying value and then you interpolate. |
Oh, wait, I think the reason we composite first is because you can have different composite modes for each endpoint of the interval. I think Alan's proposal works for this by basically interpolating twice but I'm not sure that it handles mixing accumulate and add. Perhaps that's not important. |
Ah, of course! I was thinking this was trivial with transform: none but that doesn't extend to other properties. We could try to add an "opacity" to each property which is used at compositing time but that's almost like defining generic neutral values. |
A further thought, I think we may need a neutral value concept in the API anyway. For example, consider the following CSS animation: div {
animation: move-right 2s steps(5);
}
@keyframes move-right {
to { margin-left: 100px; }
} When you call Bear in mind that CSS animations don't allow effect-level easing, only keyframe-level easing. Furthermore, each keyframe can have different easing. i.e. you could also have: @keyframes move-right {
from { margin-top: 0px; animation-timing-function: ease }
to { margin-left: 100px; margin-top: 100px; }
} (i.e. the So I wonder if we want [
{ marginLeft: null, easing: 'steps(5)', offset: 0 },
{ marginTop: '0px', easing: 'ease', offset: 0 },
{ marginLeft: '100px', marginTop: '100px', offset: 1 },
] i.e. return a This is a pretty complex issue once you introduce CSS variables and from memory I think I found there might be a need for expressing both a value that represents the underlying value (previous in the stack) and a value that represents the base value (bottom of the stack). For my own records I wrote a few comments about this on Mozilla bug 1268858. |
After thinking about this, I'm concerned switching the composite / interpolate order isn't worth the additional complexity it adds to dealing with other composite operations, also it could require multiple matrix interpolations within a single animation. I'm in favor with just trying to use neutral values which implicitly match transform list shapes in order to avoid falling back to matrix interpolation. I'm in favor of using a [
{ transform: 'translateX(100px) rotateZ(720deg)' },
{ transform: null },
{ transform: 'scale(2) rotateZ(360deg)' },
] This might allow us to do something smart like interpolate to |
Interestingly the test case for this issue, https://jsfiddle.net/flackr/1qj34byw/54/, works correctly for me in Firefox now. As does the original test case: http://jsbin.com/gazakifuma/1/edit?js,output . I believe that is due to implementing the initial spec change for issue #927 in Firefox 62. We have yet to implement the updated resolution to that issue, however. I'm curious about this issue because I'm working on implementing coalescing of forwards-filling animations in Firefox and suddenly the "neutral value" idea is interesting again. That's because in order to collapse a series of forwards filling animations that fill part-way through an interpolation interval, we'd really like to be able to combine different values with a neutral value representing the underlying value, and then simply add that result to the underlying value when we come to composite. |
I'd like to revisit this as switching the order of interpolation and composition also greatly simplifies issue #3689 and #3210, by interpolating first we can always collapse any number of additive transforms into a single matrix and have the equivalent effective transform. One of the concerns we had above was mixing composite modes but I think we can resolve this as well with the neutral value by having a partial replace be the equivalent of interpolating with neutral. I.e. suppose you have: let animationA = element.animate([
{'transform': 'none'},
{'transform': 'translateX(100px)'}],
1000);
let animationB = element.animate({'transform': 'translateY(200px)'}, 1000); At time t = 0.5, you would produce a value by
Once an animation has finished we could then replace it with the computed transform matrix and since that doesn't affect the interpolation of additional animations it will be visually identical. |
Yeah, I looked at doing this too but I'm not sure that would really solve the finished animation problem since we still need to preserve percentages, variables, context-sensitive lengths etc. |
You're absolutely right that this doesn't actually solve the general problem due to multiplication of percentages and other context-sensitive values. It might still simplify a lot of common cases though, i.e. multiple subsequent translations / scales could be internally coalesced into a single one as the shape is no longer important. I also would find this composition behavior closer to what I would expect as a developer, not having the underlying value affect the new interpolation. |
Using neutral value keyframes or
{transform: 'none', composite: 'add'}
keyframes in additive transform animations can easily cause undesired matrix interpolation to occur.Example:
https://www.youtube.com/watch?v=6Jigrf5xKTg
http://jsbin.com/gazakifuma/1/edit?js,output
One way of avoiding this is to change The effect value of a keyframe effect steps 12 and 18 to perform the keyframe interpolation before applying their composite operation.
This would change
interpolate(add(rotate(45deg), none), add(rotate(45deg), rotate(180deg)))
(which uses matrix interpolation due to therotate
->rotate rotate
shape mismatch) intoadd(rotate(45deg), interpolate(none, rotate(45deg)))
.The text was updated successfully, but these errors were encountered: