[web-animations] Rationalizing the Animation interface

Hi,

We previously resolved[1] to add some extra methods to the Animation 
interface. I've been going over this and I'm not sure how much we 
actually need to change.

First, recall the states of an Animation which we can simplify to the 
following three:

    idle = no current time (*may* have a start time)
    paused = has current time but no start time
    running = has current time and start time

'finished' is (currently) just a variation on 'running', and 'pending' 
is just an interim state while moving between one of the above three states.

We can trigger transitions between these states using three primitive 
operations:

* pause() - clears the start time (and records the current time in the 
hold time slot).
i.e. causes the following transitions
   idle -> idle [See note (g) below though]
   paused -> paused
   running -> pending -> paused
   (finished -> paused)

* play() - resolves a start time. How it does that depends on the 
current state. If there's a current time we preserve it unless the 
current time is outside the range of the animation [0, target effect 
end]. If the current time is outside that range or unresolved we choose 
a start time that makes the current time equal to the appropriate end of 
that interval.
   idle -> running
   paused -> pending -> running
   running -> running
   (finished -> running)

* cancel() - clears the start time and current time
   idle -> idle
   paused -> idle
   running -> idle
   (finished -> idle)

(There are exceptions such as when there's no active timeline but this 
is roughly how it works. There are also finer details such as which 
endpoints of the animation's range are inclusive, but let's ignore that 
for now.)

The main problems raised with this so far are:

a. play() does a conditional seek and it seems like it would be better 
to have a more primitive operation that *just* resolves the start time 
when needed (i.e. triggers the 'idle/paused -> running' transition but 
NOT the 'finished -> running' transition).

I thought about this for a while and had some ideas for introducing a 
more primitive run()/resume()/start() operation. Apart from complicating 
the API I've come to think that play() isn't that bad. We seek to one of 
the interval endpoints when transitioning from the 'idle' state so it 
doesn't seem so bad to do exactly the same thing when transitioning from 
the 'finished' state.

b. Some people want the async methods play()/pause() to return Promise 
objects.

The difficulty of returning a Promise from play() is that people might 
expect it to return a promise that resolves when the animation reaches 
the end (the finished promise). Others might it expect it to return the 
promise that resolves when the play() operation has completed the work 
necessary to transition the animation to the running state (the ready 
promise).

We could probably resolve this by adding a separate run()/resume() that 
returns the ready promise and making play() return the finished promise 
but there was some concern raised about this last time.

One of the concerns with returning Promise objects was that for Promise 
attributes, implementations can lazily create them. For return values, 
however, implementations have to create them even if they're not used. I 
think this isn't quite as bad as it first seems because for animations 
that are played/paused using CSS/SVG/Element.animate(), we wouldn't have 
to create the Promise object and that probably covers 95% or more of usage?

Another concern was that by having play() return the finished promise, 
we might encourage people to chain animations by Promise chaining. 
That's not a very good way of chaining animations because you'll get 
gaps between them and, worse still, those gaps might not be too 
noticeable on a desktop machine but only show up on mobile.

However, making play() *not* return a promise for that reason seems like 
we'd be penalizing authors with a legitimate use for Promise chaining 
(e.g. remove the div once the opacity fade has finished) for the sake of 
protecting other authors from sub-optimal usage which we could point out 
in other ways? For example, SMIL warns authors that chaining animations 
with 'begin' and 'end' is preferable to using 'beginEvent' and 
'endEvent' for the same reason and I think authors tend to get it right 
(or at least I haven't seen anyone get it wrong yet).

I don't really mind too much, but I'm trying to summarize the argument 
around this point.

c. The ready promise is reused. If you do pause() and then interrupt it 
with play() we don't create two separate Promise objects, but just reuse 
the one created when you called pause(). The idea is that the ready 
promise doesn't resolve until eveything is ready. I'm not sure if this 
is useful or not. If pause() and play() were to return Promise objects 
it would probably make more sense that they are separate 'pause-ready' 
and 'play-ready' promises which get cancelled if the operation is aborted.

d. On a related note, should we expose 'play-pending' and 
'pause-pending' as separate states? Currently they're both just reported 
as 'pending'.

e. Should 'paused' really win over 'finished'? The fundamental problem 
here is that 'paused' and 'finished' are really orthogonal. It's 
possible to be both at once. That said, I'm less than enthusiastic about 
splitting this out in the API since I think it overcomplicates it (e.g. 
if you wanted to check if the animation is actually moving you'd have to 
check anim.playState == 'running' && !anim.isFinished).

In regular circumstances you won't end up in the paused-and-finished 
state since you won't hit the end of the animation until you resume the 
animation. It's only when you're doing manual seeking or adjusting the 
duration of in-play animations that this situation arises.

If we accept the three fundamental states I proposed at the start of 
this email and treat 'finished' as a subset of 'running' then I think it 
makes sense for an animation with no start time and a current time 
outside the animation's range to be 'paused' (i.e. no change).

f. It's odd that if you're paused and call finish(), you don't end up in 
the 'finished' state. If 'finished' is a subset of 'running', then I 
think it makes sense for finish() to take you out of the paused state. 
It could do this by simply resolving the start time appropriately.

g. Should calling pause() while idle transition to the 'paused' state? 
i.e. resolve the current time to the appropriate end of the interval? To 
create an initially paused animation you currently need to call play() 
then pause() (or just set the currentTime). Amongst the three primitive 
operations introduced above there's no operation that triggers an 
idle->pause transition.


Thoughts on any of the above?

Best regards,

Brian


[1] https://lists.w3.org/Archives/Public/public-fx/2015JanMar/0072.html, 
part2

Received on Tuesday, 14 April 2015 07:39:52 UTC