web-animations / web-animations-js-legacy

The original emulator of the Web Animations specification. Please use web-animations-js instead:
https://github.com/web-animations/web-animations-js
Apache License 2.0
707 stars 84 forks source link

Composing two rotations with different transform-origin #619

Closed joeytwiddle closed 10 years ago

joeytwiddle commented 10 years ago

I was hoping to add two rotation animations to an object, and compose them.

But regardless of which order I apply these animations in, it appears that the faster animation is applied using the offset origin.

My question is: Is the behavior I am aiming for supported by the spec?

If so, let's make it work in the polyfill (I will write an example test). If not, I will need to compose these a different way (perhaps by putting one of the animations on an additional parent element).

shans commented 10 years ago

Because transformOrigin and transform are different properties, they will compose independently, so the effect isn't going to be what you're after.

i.e. I think you want (fast rotate @ transformOrigin (0, 0)) + (slow rotate @ transformOrigin (0, 200))

but what you're getting is (fast rotate + slow rotate) @ (transformOrigin (0, 0) + transformOrigin (0, 200))

Fortunately, you can emulate transformOrigin: animating translate(0px, -200px) rotate(0deg) translate(0px, 200px) to translate(0px, -200px) rotate(360deg) translate(0px, 200px)

should give you a rotation of 360deg about an origin of (0, 200).

joeytwiddle commented 10 years ago

Thanks for the explanation, and for the suggestion.

The trick using a translation before the rotate is working fine on a single animation (although I had to reverse the order). But I am still having trouble with composing animations. (A third alternative I can try would be to convert the offset rotations into matrices from the get go.)

I think I will come out with a set of tests ranging from basic to complex, to see where the difficulties arise. (RTS: Also produce the equivalent CSS3 animations where possible.) (My experiments may be compounded by the fact all my elements have an initial static matrix "animation" to perform scaling - sometimes by 1.0! I will also try with/without these in the tests to help isolate the problem.)

I wonder if we should feed this odd behavior back to the spec authors, while they are still working on it. It feels quite unintuitive that two transform-origins specified in entirely different animations (opposite ends of the app) would combine, rather than acting on the rotations declared right next to them!

This is perpendicular to the issue I had in #618, where inline transforms were being combined prematurely before interpolation with the corresponding transform in the next keyframe. (Collapsing "horizontally" this time, rather than "vertically".)

If I understand correctly, composition is one of the new features of Web Animations, so there may be room to maneuver without breaking old APIs, and this could be an opportunity to get it right. [Edit: shans says no below, our hands are tied by existing CSS!]

Can you suggest good places to ask:

One of my dumb questions is: Does the spec say that animations will overwrite (ignore) the element's existing style transform? Because that is what appears to be happening in the polyfill (and that's why I add the matrix "animation" to every element).

shans commented 10 years ago

Ah yes, I think I got the order backwards :) Sorry about that.

If you post a code snippet I might be able to help you further. There should be no essential problem with composition or addition, but some caveats:

I am actually one of the specification authors :) I agree that the interaction between transform and transform-origin is weird, but unfortunately I think it's probably out of the scope of web-animations to fix this (we are basically a means for generating time-varying values that slot into the existing style engine, and this interaction is very much a part of CSS). Nevertheless, sending an email about this to public-fx@w3.org is a great idea - don't be shy, we welcome feedback!

In terms of your questions: (1) is complicated.. if there are individual parts you're having trouble interpreting (especially if the spec isn't clear), then asking for clarification on public-fx@w3.org is fine as this is an indication the specification needs improvement. (2) This library tries to track the specification pretty closely, but there are definitely some areas that we've missed. (3) we have an irc channel on irc.w3.org (#webanimations). We'd like to limit that to specification discussions, but if your questions are pertinent to the specification then this is an appropriate place to ask them.

The specification says that by default animations will overwrite existing styles - however, this behavior is modified by specifying composite: add. So for example given foo.style.transform = 'translate(500px)';

then foo.animate([{transform: 'rotate(0deg)'}, {transform: 'rotate(100deg)'}], 1000);

will override the translate(500px) but foo.animate([{transform: 'rotate(0deg)', composite: 'add'}, {transform: 'rotate(100deg)', composite: 'add'}], 1000);

will not.

joeytwiddle commented 10 years ago

Many thanks for the clarification. I knocked up a test page and it appears to be working just fine. (Although if the animation chain or the transform string starts with a matrix then I have to use my "fix" from #618.)

So I can conclude that something is wrong in my app, not with the spec or the polyfill! Which is good news for both of us. ;)

I would still love to contribute, but if nothing needs fixing ... maybe I will look for ways to optimize in future!

The problem with my app appears to be that the initial animation:

dt.play(new Animation(divs[0], new KeyframeEffect([ foo, bar ], "replace"), timing));

is not equivalent to:

dt.play(new Animation(divs[0], [ foo, bar ], timing));

which was just a misplaced assumption on my part, and was easily rectified.

I have one more question:

As well as time-based animations, I also want to do scroll-based animations. (Which are all the rage at the moment.)

I have had success doing this by creating an animation, pausing it, and then settings its currentTime as the page is scrolled. This seems to work well. I never need to unpause the animation; the adjusted currentTime is immediately reflected in the output.

My question is: Will that behavior be supported in the final implementations, or am I just getting lucky that the polyfill happens to work that way at the moment?

This trick works nicely for a single animation, or when combining two scrolling animations. But if I try to combine a time-based animation with a scrolling animation, the scrolling animation is lost entirely. I need to track down why this happens, and may create some test examples for it too...

joeytwiddle commented 10 years ago

I was able to compose the two types of animation (paused and continuous) by dropping my initial matrix animation (which as you pointed out was not needed anyway).

document.timeline.play(new Animation(elem, [
    { transform: "matrix(1,0,0,1,0,0)" },
    { transform: "matrix(1,0,0,1,0,0)" }
], {
    direction: "normal",
    duration: 1000,
    iterations: Infinity,
    fill: "forwards",
}));   // This should do nothing, right?

618 was not enough to fix it in this case (although strangely it was enough if both the animations were continuous!).

But I still think they should have composed with the additional animation. I don't think it should have the inconsistent effect it did. So I still need to write a demo/test for this...

shans commented 10 years ago

An answer to your question: This behavior is definitely going to be supported in final implementations.

With regards to the composition on top of matrix - I'm afraid I don't have enough context to help. Could you provide a more complete code snippet?

joeytwiddle commented 10 years ago

That is fantastic news!

I will try to produce a test this week. I will base it on a copy of auto-test-composite-transforms.html. I have just noticed each of those tests has a corresponding .js file, so the test can actually run automatically, so I will create that too...