Web Animations/API Primitives

From Effects Task Force
Jump to: navigation, search

These are just some thoughts to aid more concrete discussion

Overview

3 basic objects:

  • Animator
    • a description of an animation effect (i.e. a template)
    • generates Animation objects
    • contains timing information as attributes
    • contains an AnimationFunction object that defines animation values
  • Animation
    • an instance of an animation
    • has a target element / list of target elements
    • contains timing information including actual start and end times (i.e. resolved times)
    • contains an AnimationFunction object which has computed animation values?
  • AnimationFunction
    • a series of animation values for a range of properties on an element


Overview of proposed object primitives

Interface

interface Animator {
  AnimationList animate(NodeList targets, delay);
  attribute Duration duration;
  attribute float iterationCount;
  attribute unsigned short direction;
  attribute unsigned short reversing;
  attribute Duration delay;
  attribute AnimationFunction function;
};
  • proposal<shans>: Animators have two constructors. One requires the interface parameters directly (duration, iterationCount, etc.), and the other accepts an ordered stack of existing Animators. The second version generates interface parameters as required by walking the stack of Animators provided in the constructor. This means that we'll need to modify the interface somewhat so that we can tell which attributes are explicit and which are defaults.
    • note<birtles>: I'm wondering how necessary the stack-based approach is. It seems like there would be a number of ways we could provide access to Animations/Animators without it. e.g.
      • elem.getAnimations() – returns currently active Animations from which you can fetch the corresponding Animator, or
      • window.getAnimatedStyle(elem) – (i.e. parallels getComputedStyle) returns the Animator corresponding to the CSS animation properties resolved at that point. It doesn't even need to return the same object as is returned for other elements that have the same style applied.
        • If we decided to allow the Animator objects to differ between elements with the same style you could make such Animator objects (a) copies (i.e. non-live), (b) read-only, or, (c) map changes to element style (i.e. not effect the style declaration that generated them).
    • My concern is just that having a stack is complicated for the script to navigate. There's also the lesser concerns that:
      1. we're binding the API too much to CSS, and
      2. that having lots of unique chained objects like this seems like it might limit optimisation opportunities for the implementation.
    • But more than those, I just want to be sure the complexity here is necessary.
    • note<shans>: The case for the stack-based approach comes from performance considerations as well, not just access ones. But basically, I think we want to make sure there's a a one-to-one correspondence between Animators and CSS rules that contain transition / animation properties.
      • elem.getAnimations() - ok, there's a set of Animations, and you can get to the Animators. What do the Animators correspond to in the CSS (see examples below)? How do you get from the Animator exposed here back to the raw CSS rules that generated them? There's a many (rules) to one (Animator) correspondence here.
      • window.getAnimatedStyle(elem) - this has the same problems.
    • Concerns about complications from a script perspective - actually, this isn't as bad as it appears at first blush. Consider the following tasks:
      • (1) An author wants to access 'the Animator' that is driving an animation on an element, so that they can change/inspect some behavior of the current animation (if we even allow changes). For this purpose, the Animator that hangs off the Animation is perfect.
      • (2) An author wants to access the underlying rules in order to poke at the way the animation occurs in the future. There's fundamentally no right way to do this for CSS animations unless you're basically changing the CSS rules themselves. Having an Animator per CSS rule makes this possible without having to descend into the murky depths of the CSSOM.
    • Lesser concerns
      • binding the API to CSS - yes this is a potential issue. However I actually think stackable Animators might be useful in other contexts too. Give me some time to think of some :)
      • unique chained objects - I think we fundamentally need an object to represent each CSS rule's view of the animation, and an object to represent the composition of those rules. There's no way around this if we want to remain faithful to CSS.
    • note<birtles>: Ok, I think my main question here is why do we want an API to map to each CSS rule? If you really want to change the CSS rules, use CSSStyleDeclaration. Generally, I think you want the animation (meaning Animator here) corresponding to a particular element so you can:
      1. apply it elsewhere, or
      2. change its parameters for the particular element you fetch it for.
    • If you want to globally change the behaviour of a particular kind of animation via script you either:
      1. look up the appropriate style rule you want to change and modify it there via CSSStyleDeclaration, or
      2. write it with script to begin with and apply it to a NodeList.
  • Having created an Animator object (or after retrieving the Animator object corresponding to a CSS or SVG Animation) you can trigger new animations like so:
animator.animate(document.body.querySelectorAll('.star'));
  • Current animate returns an AnimationList, i.e. one Animation per target element. I suspect it's better though to have an Animation object target a NodeList so you can pause and cancel all animations at once. (I think it's unlikely you'd call animate() on a NodeList and want to cancel the effect on just one of the nodes. If you did, you'd probably trigger it separately.)
interface Animation {
  bool pause();
  bool isPaused();
  bool cancel();
  readonly Time beginTime;
  readonly Element target;
  readonly attribute unsigned short direction;
  readonly attribute unsigned short reversing;
  readonly attribute Duration delay;
  readonly attribute AnimationFunction function;
};
  • Contains a readonly snapshot copy of the data from the Animator that generated it with the exception that times are resolved and animation values are calculated values.
    • In practice this could be implemented as copy-on-write so it needn't be inefficient.
    • This is to correspond with the behaviour of CSS, "The values used for the keyframes and animation properties are snapshotted at the time the animation starts. Changing them during the execution of the animation has no effect."[1]
      • note<shans>: We can comply with CSS behaviour in the CSS layer if necessary - we should decide whether we want to be able to modify these properties on the fly or not and set the API accordingly.
      • note<birtles>: Definitely agree here. So far I haven't been able to think of a reason why you'd want to change properties on the fly. One possibility is adjusting the duration, but I think that's better handled declaratively (e.g. dur="auto" and then adjusting whatever it's relative to.) Or, alternatively, add API for specifically handling those cases where it's useful (e.g. make Animation.duration write-able) and then define clearly what happens when those things change.
        The attractive thing about adopting the CSS behaviour is that:
        1. it's simple,
        2. it rewards authors' expectations with consistent behaviour.
interface AnimationFunction {
  attribute Animator animator; // useful?
  attribute attribute/propertyName;
  attribute values[];
  attribute offsets[]; // keyTimes
  attribute timingFunctions[]; // keySplines etc.
  attribute additive;
  attribute accumulate;
  attribute fill;
};
  • For a given element, targeted by an Animation, we would like to be able to animate multiple properties/attributes. So we could:
    • Have one AnimationFunction per target property / attribute. Simple, but more objects to manage. As above. The parallel arrays aren't great, so alternatively we could have,
interface AnimationFunction {
  attribute attribute/propertyName;
  attribute AnimationFrame[] frames;
  attribute additive;
  attribute accumulate;
  attribute fill;
};

interface AnimationFrame {
  attribute value;
  attribute offset;
  attribute timingFunction;
};
    • Have one AnimationFunction per Animation, i.e. targets multiple properties/attributes. e.g.
interface AnimationFunction {
  attribute AnimationFrame frames[];
  attribute additive;
  attribute accumulate;
  attribute fill;
};

interface AnimationFrame {
  attribute offset;
  attribute timingFunction;
  attribute values; // A dictionary attributeName -> value? CSSStyleDeclaration?
};
      • Matches what CSS Animations does, but not very flexible if you want to have a key frame for opacity at 30% and a key frame for width at 50%.
      • Also you really want to specify additive/accumulate/fill behaviour per attribute I think.

Mapping to SVG

  • An <animate> element contains an Animator object. Better still, the SVGAnimationElement interface inherits from Animator. Not sure what WebIDL/JS etc. allow wrt to MI however.
  • begin="5s; 10s" would produce two Animation objects (with begin time 0s and delay 5s and 10s respectively).
    • Similarly begin="rect.click; animB.begin+2s" would produce Animation objects as required (effectively calling animate on itself).
  • In SVG 1.1, the target would always be singular, but in SVG 2 I'd like to support, e.g.
<animate select="p.warning" attributeName="background-color" ... />
  • Most of SVG's animation attributes fall into two categories: timing and animation. Here the timing ones mostly map to the Animator/Animation objects. The animation ones map to the AnimationFunction.

Mapping to CSS

  • A CSSKeyFrames rule corresponds to an AnimationFunction.
    • Not sure how to line up these APIs yet... maybe we don't need to?
    • Maybe make CSSKeyframesRule inherit AnimationFunction?
  • A set of animation-* properties corresponds to an Animator.
    • The Animator generates Animations whenever an element is matched.
    • The tricky thing is it's not just one CSS style declaration block. It's basically when the style gets resolved that you create the Animator and (in the current arrangement) the Animation too.
    • I'm not sure how you get the Animator object or the Animation object either.
      • Presumably animation events would contain a reference to their Animation object (for cancelling etc.) which would have a reference back to its Animator.
      • Also, maybe when you look up the computed style you'd be able to get the Animator object?

Simple CSS case study

This section should be considered proposal<shans>.

Very basic example

.foo {
  color: red;
  transition-property: color;
  transition-duration: 1s;
}

.foo:hover {
  color: green;
}

A single Animator will be constructed at CSS parse time, with default iterationCount, direction, reversing, and delay, and a duration of 1s.

When the hover event occurs on a DOM element with class foo, the CSS engine must notice that a transitionable property has changed. This is relatively easy, as the combination of old computed style (color: red) and new computed style (transition-property: color; color: green) readily yields the fact of a change.

The engine will construct a new Animator around the stack of Animators stored in style objects that impact the element. In this case, both rules above impact the element, but only one provides an Animator. In the absence of other rules, the stack will therefore have one member, and the new Animator will effectively inherit the properties of the singleton in the stack directly.

The new Animator is then provided with an animationFunction. It must be done at this stage and not earlier because for CSS the *current* (i.e. just prior to application of the new rules) property value must be used as the 'from' value of the transition, and the 'to' value is dictated by the set of rules that will apply once :hover is taken into account. So we set animationFunction: attribute=color, values=[red, green], offsets=[0, 1].

The animate method of the new Animator is then called, and provided with the DOM element that has been hovered.

More complicated example

.foo {
  color: red;
  transition-property: color;
  transition-duration: 1s;
  transition-timing-function: linear;
}

.bar {
  transition-delay: 2s;
  transition-duration: 2s;
}

.foo:hover {
  color: green;
}

.bar:hover {
  color: white;
}

This example illustrates why both the stack is necessary and late generation of the animationFunction is necessary. Consider three dom elements, one with class foo, one with class bar, and one with both classes. In each case the element gets hovered.

The .foo element has a transition-property of 'color', a transition-duration of '1s', a transition-delay of '0s', a transition-timing-function of 'linear', and a color of 'red'. After being hovered, the color changes to 'green'. The .bar element has a transition-property of 'none', a transition-duration of '2s', a transition-delay of '2s', a transition-timing-function of 'ease', and a color of 'red'. After being hovered, the color changes to 'white'. The .foo.bar element has a transition-property of 'color', a transition-duration of '2s', a transition-delay of '2s', a transition-timing-function of 'linear', and a color of 'red'. After being hovered, the color changes to 'white'.

At CSS parse time, two Animators are created, one attached to the .foo rule, and one attached to the .bar rule. The .foo Animator has duration: 1s and timing-function: linear, while the .bar Animator has duration: 2s and delay: 2s. All other properties for both Animators are defaults.

At hover time, these Animators are organized into stacks that match the applied rules for each element. The .foo element generates a stack of just the .foo Animator; while the .foo.bar element generates a stack of the .foo Animator followed by the .bar Animator. The .bar element does not generate a stack as no transitionable property changes on this element.

AnimationFunctions are then generated for .foo and .foo.bar, based on the immediately preceding and new values for the transitionable properties (red -> green for .foo, red -> white for .foo.bar). Finally, the animate() function is called and each element transitions appropriately.

Simple API for CSS integration

thinking-out-loud<birtles>:

Here's an attempt at a simple integration of the API with CSS.

I've taken a different approach to the stack-based analysis. Actually, much of this overlaps. The timing of the creation of Animator objects and AnimationFunction objects makes a lot of sense and is in common with the proposal here.

What I'm interested in here is "how does script access these objects?" Specifically, what are the use cases where access to the stack is actually needed? I've outlined what I think are common uses and then tried to work out the simplest way to address them.

Use cases

1) I have an element, I want whatever animations apply to it to apply somewhere else (the 'prototype' use case).

There's a method somewhere to fetch the Animations targetting an element. It might hang off the element itself (e.g. elem.animations) but probably it's more likely that it hangs off the window or document object, e.g. window.getAnimatedStyle(elem).

It returns a list of all the Animation objects currently active on this element. These Animation objects may be created by CSS, SVG, or script. It doesn't matter.

(I'm a little unsure if we should return the Animators or the Animations. At first I thought it was the Animators, but then how do you get the Animations to, e.g., cancel them all? I think it makes sense to get the Animations and go backwards to the Animator.)

UPDATE: I think we actually want:
  1. element.animations – returns a list of Animation objects so you can cancel them etc.
  2. window.getAnimatedStyle(element) – returns a list of Animator objects so you can change future animations as outlined below.

I've updated the below to reflect this.

The reason it's a list is that many independent Animations can target an element. We have to define the priority system for overriding/addition. Also, I imagine a CSS declaration such as: animation-name: a, b results in two Animators and two Animations.

Now, what we could do is, something like this:

var animators = window.getAnimatedStyle(prototypeElem);
for (var i = 0; i < animators.length; ++i) {
  animators[i].animate(targetElem);
}

And sometimes that will be what you want. Othertimes what you really want is for all the declarative triggers defined for prototypeElem to apply to targetElem but that's a totally different problem. It means you really want transitions defined (including pseudo-class selectors) to apply, as well as stuff like:

<animate begin="accessKey(a)" ... />

to apply. In that case you're better off just defining an appropriate class and doing targetElem.classList.add("anim"). Note that this applies to SVG animations too since I'm proposing they support a select=".bar" syntax.

Alternatively, we can add a list of default targets to the Animator interface and… I'm not sure it's worth it.

In either case, the presence of absence of a stack doesn't make any difference here.

2) I have an element which is currently swelling, and I just know I want to make it swell twice as big.

var re = /scale\((\d+)\)/;
var animations = swellingElem.animations;
for (var i = 0; i < animations.length; ++i) {
  for (var j = 0; j < animations[i].functions.length; ++i) {
    var func = animations[i].functions[j];
    if (func.property == 'transform') {
      var lastFrame = func.frames[func.frames.length-1];
      lastFrame.value =
        lastFrame.value.replace(re,
        function(str, amt, offset, s) {
          return "scale(" + parseFloat(amt) * 2 + ")";
        });
    }
  }
}

The important things to note here are:

  1. It doesn't matter where the animation is defined (CSS, SVG, or script)
  2. If it's defined in CSS, it doesn't update the stylesheet, just the currently running animation.

3) I have an element which has a swell transition defined on it, and I just know I want to make it swell twice as big when it swells.

This gets interesting because you really want to navigate the stylesheet and get back an Animator interface to a rule. The question of how to do this navigation is the same whether you have a stack or not, but the result is different.

This is where you use window.getAnimatedStyle(elem) (which like getComputedStyle also supports a pseudo-element param so you can do window.getAnimatedStyle(elem, ":hover")).

For the "complicated example" above, I think you'd get:

window.getAnimatedStyle(elem)
 Simple API → Animator[ { duration: 2s, delay: 2s,
                          timingFunction: linear } ]
 Stacked API → Animator[ [ { duration: 2s, delay: 2s, // fully-resolved
                             timingFunction: linear },
                           { duration: 2s, delay: 2s }, // .bar selector
                           { duration: 1s, // .foo selector
                             timingFunction: linear } ] ]


Is that right? In either case, you get a fully-resolved set of properties? But with the stacked one you can go back a few levels?

comment<shans> Not quite. You'd get:


Stacked API → Animator{ [ Animator{duration: 2s, delay: 2s}, // .bar selector
                          Animator{duration: 1s, timingFunction: linear} // .foo selector
                        ] } 

The fully-resolved Animator is exactly the outer Animator, which determines its values by walking the list. In answer to your question below, taking the outer Animator as the fully-resolved set, if you modify this Animator you:

  1. will update any future Animations minted off this Animator
  2. might update the current Animations minted off this Animator (I guess we haven't decided this yet? :))
  3. will not update any Animators that are directly associated with Style Rules, and therefore will not modify any CSS Styles.

On the other hand, if you retrieve one of the inner Animators (either by walking the list via the outer Animator, or by an API directly off a Style Rule) then I think modifications to the Animator should update the CSS Style.

questions<birtles> I don't understand what the outer Animator corresponds to here. Is it the styles as applied to this element? I guess so right since the use case here is to update future animations on a particular element. If that's the case then the questions under 3 remain—are changes serialised? do we update element style? (has implications for SVG animations) do we generate new CSSKeyFrames rules?

note<shans> Yes, the outer Animator is styles as resolved for the element. So I think the answers to those questions are evident: the changes are not serialised and the element style is not updated because this is a synthesis of CSS Rules. Only the computed values in the outer Animator are modified, and this only impacts future Animations generated from the Animator. Further, this Animator is only relevant to the current element at the current time, so once the current animation on that element has finished the Animator will be destroyed.

questions<birtles> Note also, that I'm proposing that getAnimatedStyle returns a list of all Animators targeting the given element (since animation-name can specify multiple animations, and others Animators might be defined in SVG) so I think the above would be:

Stacked API → Animator[ { duration: 2s, delay: 2s,
                          timingFunction: linear,
                          stack: [
                            { duration: 2s, delay: 2s }, // .bar selector
                            { duration: 1s, timingFunction: linear } // .foo selector
                          ]
                      } ]

The Animator[] is just indicating the type of the array. Sorry, that's probably quite confusing. Also, I realise that the duration etc. in the outer object above are calculated, but from a DOM point of view they will just appear as regular properties.

So it's basically the same as the Simple API here but with an extra stack property (probably called something else like context I guess).

comment<shans> That's correct, yes.

In either case the question remains, what happens if you update the fully-resolved set? Does it:

  1. throw an exception because it's read-only,
  2. remember where the property was most specifically defined and update that style rule (this is a bit problematic because we got the animated style for a single element but now we are updating the style of multiple elements), or
  3. add the properties to the element style (this assumes SVG will honour animation properties on target elements; also, if you change the animation function, does it cause a new CSSKeyFrames rule to be generated? And if so, is it serialised or just in-memory? I think this question arises for 2 as well actually).

4) I know I have a style that targets all buttons with class "bid" and makes them pulse red and I want to decrease the duration of the animation because the auction is about to finish.

If know I have that style because I can see the stylesheet then I can just look up the stylesheet and fetch the rule.

var rule = getStyleRuleWithSelector("button.bid");
if (rule) {
  rule.setProperty('animation-duration', '0.5s');
}

function getStyleRuleWithSelector(selector) {
  var stylesheet = document.styleSheets[0];
  for (var i = 0; i < stylesheet.cssRules.length; ++i) {
    var rule = stylesheet.cssRules[i];
    if (rule.type == CSSRule.STYLE_RULE && rule.selectorText == selector)
      return rule;
  }
  return null;
}

If I know I have that style but can't see the stylesheet (not sure why that would be) then the other route would be to create a new style rule to override the particular property (and hope it's got more specifity than the other rule, or whack on "!important" for good measure and duck for cover).

comment<shans>: With the stacked approach, I can also extract the overall Animator via the button's getAnimatedStyle method, then walk its stack to get the style's Animator.



comment<birtles>: Does that mean Animator's record the selector they correspond to? If not you can't do it safely. I can search the doc for a <button> with class "bid" easily enough but when I walk the stack of Animators I don't know if I'm looking at the Animator that corresponds to "button.bid" since there may be another more specific rule that targets the particular representative button I found.

Furthermore, I should be able to perform the operation regardless of whether there currently exist any "bid" buttons in the document, i.e. I shouldn't need to find a representative button. Such buttons might get added shortly after making the modification to the animation definition.

5) The Web Animations group has made this new feature which lets you define the timing function using catmull-rom curves, or even via script. But it's not in CSS yet ☹

Firstly, I don't know how likely this is—if CSS Animations gets merged into Web Animations then it's probably not going to happen often. You wouldn't get the API and CSS out of sync. If there was something not in CSS, there would probably be a good reason for it. Also, if implementers support catmull-rom curves in the API, they're probably going to expose it to CSS anyway.

comment<shans>: I strongly disagree that this is unlikely. The CSSWG will want to have input on how these features are exposed syntactically, and that's likely to take months to years in some cases. Furthermore, you've conflated "a good reason to omit this feature from CSS" with "a good reason to want to avoid this feature". Lack of a convenient declarative syntax is a really good reason to omit support from a Web Animations feature in CSS, but that doesn't automatically make it a feature to avoid.

But supposing this situation does come up, and suppose I want to use this new feature on my '.bar' selector (from above).

One possibility, if we use behaviour 2 from use case 3, is as follows:

var animators = window.getAnimatedStyle(representativeElem);
// Find animator that targets the property in question
// like what we do in use case 2
// Use fancy new Catmull-Rom syntax
animator.timingFunction = "M20,380R58,342 100,342 100,300";

But finding a representative element is probably hard. Another possibility is to overload window.getAnimatedStyle so it takes a string representing a selector and it then finds all style rules with that selector and returns Animator objects for each:

var animator = window.getAnimatedStyle(".foo")[0];
animator.timingFunction = "M20,380R58,342 100,342 100,300";

Alternatively, you just say "that feature is not in CSS Animations, you need to do it with script or SVG". That's what we do with Filters, and that's a large part of the reason for supporting an element syntax, to keep the CSS syntax simple and elegant.

If it's not in SVG you still just do:

var animator = document.getElementsByTagName("animate")[0];
// Use fancy new Catmull-Rom syntax
animator.timingFunction = "M20,380R58,342 100,342 100,300";

I'm already proposing we allow animations defined using SVG syntax to be referenced via animation-name in CSS so you'd still get all the benefits of being able to use such an animation in your stylesheet.

comment<shans>: Couple of points here:
  1. If you overload window.getAnimatedStyle to return Animators for Style Rules (in fact, if you posit the existence of Animators matching Style Rules directly at all), then I think we're very nearly in the same place. Basically, if I've got a bunch of Style Rule derived Animators that impact an Element, and I need to calculate an Animation for that element, then I need ... something ... that takes the Animators, places them in order, and determines the actual current value of properties like transition-name based on the ordered list of Style Rule Animators.
  2. Your alternative ("that feature is not in CSS Animations, you need to do it with script or SVG") will work, but I think it would be upsetting to allow effective extension of the Animators via SVG but prevent this from happening in CSS. Introducing a single indirection (an Animator that references a list of underlying Animators) means that the CSS model is also open to manipulation in this manner, and merely formalises a process that has to happen anyway.

comment<birtles>: Things I'm concerned about with the stack approach:

  • Complexity for authors—not a big deal since, if my understanding above with regard to the stack property (or context or whatever we call it) is correct, it's easy enough for authors to ignore it.
  • Imposing too many constraints on how implementations represent style resolution. I know WebKit builds up styles as a stack but should we require all user agents to do the same as WebKit here? I freely admit I don't really know what I'm talking about here, but for example, if we have the following:
.foo {
  transition-duration: 1s;
}
.foo {
  transition-duration: 2s;
}
    • It seems like an implementation should be able to internally just remember, .footransition-duration: 2s? Of course, if the user walks the stylesheet they'll need to generate objects for the first rule too, but in terms of resolving animation styles, it should be safe to discard the first rule right? With the stack API, you'd have to at least remember that there was another rule with animation properties so you could later generate an Animator object right?
  • If you want to change styles, you need to work on the stylesheet, otherwise you don't know what you're changing (unless we allow Animators to expose the selectors that they correspond to). The window.getAnimatedStyle(".foo") possibility at least gives you that context.

I think the idea of saying "use SVG or script" is reasonable if we agree that SVG will have a bigger feature set than CSS. Maybe there's a difference there? If, like Filters, we take the approach that for simple stuff you can use CSS, but for everything else use the element syntax, then I think it's quite reasonable to expect people to switch to the more fully-featured syntax to use newer functionality. But again, that depends if we agree on the relationship with SVG/element syntax here.

It seems to me the main requirement that the stack API addressses, is to provide a typed API to style rules with animation properties for the purposes of (a) modification, and (b) extension. My preference is to do without such an API and address (a) with the existing CSS interfaces and (b) with SVG/script as described above. However, I'm ok with providing a typed API for such rules along the lines of window.getAnimatedStyle(".foo"), i.e. one that is:

  1. CSS-specific since you're doing a CSS-specific operation (if you have use cases for using stacks with SVG then I'd be pretty keen to hear them)
  2. makes the context of the operation clear (i.e. you know what selector you're dealing with)
  3. doesn't impose restrictions on how implementations represent the style cascade

As for having "something" that "takes the Animators, places them in order, and determines the actual current value of properties like transition-name based on the ordered list of Style Rule Animators", my expectation is that implementations won't create Animator objects unless requested. To resolve style properties the implementation will just follow it's usual CSS processing. That is, the "something" is already there. When an Animator object is requested it can be generated (i.e. the tear-off pattern).

And yet, having written all of that, I find myself strangely open to the idea of stacks after all ☺

6) I have an element in my DOM that is animating in an unexpected way. What's going on?!

You can inspect the current animations on the element by using var animations = element.animations. This will tell you how the element is behaving, but not why those particular animations are in place. Let's say you trace the problematic behaviour down to a single animation - where did it come from? The first thing to do is to extract the Animator: var animator = animations[i].animator. But now we need to know what the Animator is. There are a few possibilities:

  1. this animator comes from an SVG style that applies to this element directly.
  2. this animator comes from a set of CSS styles that apply to this element.
  3. this animator was generated via the JavaScript API and targets this element via the use of animator.animate().
  4. this animator comes from SVG but does not naturally apply to this element at all, but has been asked to target this element via the use of animator.animate().
  5. this animator comes from CSS but does not naturally apply to this element at all, but has been asked to target this element via the use of animator.animate().


I think it's fairly clear we need some kind of traceability (1) from an animator back to the CSS rules or SVG elements that define it, and (2) on the kind of link between an animator and its animation (CSS, SVG, script). Without these two pieces of information, debugging in general could get very difficult.

7) I have a complex set of animations on this page and I know I want to tweak the effect of *this* CSS rule on *this* element only.

I don't think this is possible in any of the proposals so far :)

  • we can't tweak the animation that matches the CSS rule and the element, because that loses the effect of the style cascade.
  • we can't tweak the animator that matches the CSS rule (or the CSS rule directly), because that effects all future applications of the rule for all elements, not just for this element
  • we can't tweak the animator (if it exists) that matches the CSS cascade, because that only impacts elements in the future and may not exist after the current invocation.

The closest we can get is something like this (assuming that we have cssAnimator = animator derived from style rule directly, compositeAnimator = animator derived from stack of style rules, element = element in question):

newComposite = compositeAnimator.clone();
newCss = cssAnimator.clone()
// make changes to newCss ...
i = compositeAnimator.stack.index(cssAnimator)
newComposite.stack[i] = newCss
t = animation.getTime()
animation.stop()
newAnimation = newComposite.animate(element)
newAnimation.setTime(t)

Note that in order to be able to do this we need to be able to locate cssAnimator and compositeAnimator, which requires similar mechanisms to use case 6.

Additive behaviour

I'm not sure yet how additive behaviour will work, but I think it's important to recognise that addition will probably work on the property/attribute level not the element level. So I'm a bit worried that if you have an element-level stack corresponding to the CSS cascade along with a property-level list corresponding to the animation priority you'd get tangled in lists. Maybe this is something that the element.animations vs window.getAnimatedStyle APIs can help separate out.