Re: [web-animations] Rationalizing the Animation interface

>
> 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.)
>

This is a really good summary of the different states and how to transition
between them. Could we insert this in the non-normative text of the spec?


> 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.
>

One difference between transitioning from idle and transitioning from
finished is that we don't have a currentTime in idle, so we *need* to
construct one. So these aren't really the same thing.


> 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.
>

Good summary. I think there's still enough uncertainty around this to punt
on promises as return values in level 1. It's something we can easily
introduce later, but changing once introduced will be much harder.

What makes me say this especially is your performance argument. We just
don't know what typical usage will look like until multiple vendors have
implemented the spec.


> 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.
>

This is probably worth thinking about from a use-case perspective. Are
there situations we can think of where it's important to know that the
pause() was cancelled?

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.
>

It does from the perspective of the states, but also it doesn't make sense
intuitively for finish() to unpause you.

Maybe we don't need finish()? It's trivial to implement yourself
(player.currentTime = player.endTime).

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.
>

That sounds like a good idea.

Cheers,
    -Shane

Received on Thursday, 23 April 2015 22:13:39 UTC