- From: Brian Birtles <bbirtles@mozilla.com>
- Date: Tue, 14 Apr 2015 16:39:20 +0900
- To: "public-fx@w3.org" <public-fx@w3.org>
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