Last time I introduced timing groups in Web Animations as a simple yet powerful tool for synchronising animations. Great as they are, they open up a few interesting questions. For example, what happens when you pause an animation that’s in a timing group?
Consider the following parallel timing group. It has two child animations, A and B, and the group repeats twice.
Suppose we pause child animation A at the red down arrow and then resume it at the red up arrow. What should happen?
One option is to just let everything play as normal.
But then what happens to the second run of A? Does it run at all? Get squashed? Get overlapped? And if so, who wins?
So what if we automatically unpause animation A when we hit an iteration boundary?
From an API point-of-view that automatic unpausing is tricky and likely to lead to bugs where a piece of script assumes an animation is paused but on some devices the interval boundary occurs before the script gets a chance to run, breaking the assumption.
Another approach is just to stretch the iteration to fit the pause like so.
This mostly works for parallel containers but, like the other behaviours, it doesn’t make sense for a sequence timing group because the sequence is broken.
Also, all these behaviours are problematic if we seek the group whilst a child is paused. And if we seek backwards after unpausing what is the expected state of the world?
The approach that provides the most intuitive behaviour in these situations is simply to pause the group as a whole like so.
This of course applies all the way up the tree.
But what do other APIs do?
Looking at a number of other animation APIs:
- QML just ignores play control on animations if they’re in a group.
- Android doesn’t include repeating etc. on groups and animations and groups don’t have play control.
- In WPF only the root clock can be interactively controlled.
- In HTML, media slaved to a controller (similar in some sense to a timing group) behaves differently to independent media. You can pause it independently, but seeking throws an exception. Pausing due to buffering pauses the controller. There is no repeating.
In summary, QML and WPF push pausing to the root of timing hierarchy. Android and HTML manage to do something different but only because they don’t allow groups to repeat.
For Web Animations, like QML and WPF, the best option is to push pausing and other play control to the root of the timing hierarchy. The trouble comes when we try to represent this in the API.
At first we tried to do this transparently, that is, you’d call pause
and it
would just go up to the top-level timing group and apply the pause there.
However, a couple of concerns were raised with this,
- It’s not obvious that the operation you’re performing may have wide-reaching effects.
- It doesn’t work for seeking since the time values are relative to the child not the parent. HTML just throws an exception in this case, but some people felt uncomfortable that this method would sometimes work and sometimes throw an exception depending on whether there was a parent timing group or not.
Enter the players
In order to emphasise the fact that play control (pausing, seeking, etc.) takes place at the root-most timing group a new concept, the player, was introduced. A player plays things. It’s the point of attachment between some timed content and a timeline. The arrangement is as follows.
From a code point of view it looks like this:
var anim = elem.animate({ left: '20px' }, 3);
anim.player.pause();
This separation hopefully makes it more obvious to an author that the change they are making may have more wide-reaching effects.
It also can make avoiding some common coding mistakes a little easier.
// The following could cause some animations to be sped up 4, 8 times or more
elem.getCurrentAnimations().forEach(function (anim) {
anim.player.playbackRate *= 2;
});
// The following will speed everything up by a factor of 2 and no more
elem.getCurrentPlayers().forEach(function (player) {
player.playbackRate *= 2;
});
It also has a few nice properties:
TimedItem.localTime
is always readonly and has a different name to the always writeable Player.currentTime.TimedItem.timing.playbackRate
affects the model in the same way as changing other timing parameters does including possibly causing the playback position to jump.Player.playbackRate
, on the other hand, makes dynamic changes so that the current playback position is preserved.
Unfortunately, introducing the idea of players adds conceptual weight to the API. It’s probably one of the most unfamiliar concepts for people coming to the specification (hence this blog post!). For these reasons I resisted this change strongly but ultimately lost. 🙂
That said, it does make the model more consistent and therefore, to some degree, more intuitive. It is also expected that many authors will never use this functionality (jQuery, for example, does not provide pause control) and so this additional concept shouldn’t become a barrier to adoption for simple usage.
I’ll look forward to seeing how authors take to this concept. If you have any
feedback, feel free to follow-up at public-fx@w3.org
with subject starting [web-animations]
.
Upcoming changes
Recently we’ve been discussing some changes to the way players work. Currently
they just keep playing forever but we think it might make sense for them to
behave more like a VCR and stop when they hit the end of their media. This would
allow us to define a reverse()
method that behaves sensibly and perhaps make
players then
-able.
You can see some of the discussion in the minutes from our recent telcon, item 6, Making players stop. As always, your comments are welcome at public-fx@w3.org.