Re: [csswg-drafts] Allow adjacent sets of filling animations to be coalesced

## Analysis of the problem

### Issue 1: Specified values can depend on context

Animations can be defined with property values such as `10em`, `var(--yellowish)`, or `50vh`. The corresponding computed values will depend on context that can change over time.

Consider an animation that moves an element to `left: 90vw`. The expectation is that even after this animation has finished and is filling, if the user rotates their screen then the element's `left` position  represents `90vw` of the updated viewport dimensions (and not, for example, the computed value of `90vw` at the point when the animation finished).

When several animations affecting the same property of the same element have finished and are filling, the final animated value of the property might be the combination of a number of such context-sensitive values (e.g. `1rem` + `2em` + `var(--active-nudge-width)`). If the context changes, the computed value of any one of these values might update affecting the end value. As a result UAs must maintain all the specified values of filling animations where additive animations are used.

Furthermore, this problem does not merely apply to additive animations (i.e. animations with composite operations `add` or `accumulate` as part of their fill value) but also to animations that use implicit 0% or 100% keyframes. That is because such animations might fill mid-way through an interval, e.g.:

```js
elem.animate({ left: '5em' }, { duration: 1000, iterations: 0.5, fill: 'forwards' });
elem.animate({ left: '50vw' }, { duration: 1000, fill: 'forwards' });
```

In the above example the fill result will be `50vw` + `2.5em`.

### Issue 2: Operations cannot be re-ordered to create an intermediate result independent of the underlying value

To calculate the animated value of a property, a [stack of animation effects](https://drafts.csswg.org/web-animations-1/#effect-stack) is generated. In order to address the memory leak identified in this issue we would like to coalesce adjacent filling effects within this stack.

Even supposing issue 1 can be resolved such that we do not need to maintain the specified values of such effects, we face another problem: we cannot always calculate the result of an effect independent of its underlying value. As a result, we cannot coalesce effects.

In general, the result of a filling keyframe effect for a single property is as follows:

> result = (L acc(n) (U comp<sub>A</sub> A)) interp(p) (L acc(n) (U comp<sub>B</sub> B))

where:

* `L` = value of the last keyframe at offset 1.0 (if any)
* `acc(n)` = accumulate operation applied `n` times to LHS, with RHS applied after that
* `U` = underlying value
* `comp`<sub>A</sub>,<sub>B</sub> = composite operator for value `A` / `B`
* `A` = the ‘to value’ being interpolated from at progress `p`
* `interp(p)` = interpolate operation at progress `p`
* `B` = the ‘from value’ being interpolated to at progress `p`

In order to be able to calculate this result independent of `U` so that we can combine it with adjacent effects we would need to factor out `U` such that it is an independent term in the above expression.

For example, if we could somehow re-write the above along the lines of:

> result = (U interp(p) ∅) comp<sub>A</sub> (U interp(p-1) ∅) comp<sub>B</sub> ((L acc(n) A) interp(p) (L acc(n) B))

(where ∅ represents a no-op/neutral value as described in #2204)

we could most likely represent a section of the stack as a component that is independent of the underlying value (the `((L acc(n) A) interp(p) (L acc(n) B))` part) plus some portions of the underlying value.

For many properties this is possible. For example, since lengths are combined using addition and multiplication, we can trivially factor out the underlying component and add it later.

However, consider transform lists. Interpolation and accumulation produce different results based on the shape of the inputs and are therefore not only not commutative (neither is addition) but nor are they associative.

For example, suppose we have an animation that fills forwards at the midpoint of an interval between ‘rotate(30deg)’ and ‘rotate(390deg)’. Furthermore, suppose both values have composite operation ‘accumulate’. The animated fill value is calculated as:

> result = (U accumulate ‘rotate(30deg)’)) interp(0.5) (U accumulate ‘rotate(390deg)’)

Now suppose `U`, the underlying value, is ‘translate(10px)’. Since ‘rotate(x)’ and ‘translate(x)’ have a different shape, we will convert both to matrices first, and then accumulate them. However since ‘rotate(30deg)’ and ‘rotate(390deg)’ have the same matrix representation, when we interpolate the result we will end up with just the matrix-equivalent of ‘translate(10px) rotate(30deg)’.

If we try to factor out a component that is independent of the underlying value (e.g. by substituting 'none' for `U`) and interpolate just ‘rotate(30deg)’ and ‘rotate(390deg)’, we will get ‘rotate(210deg)’. If we subsequently accumulate that with `U` of 'translate(10px)' will end up producing ‘translate(10px) rotate(210deg)’ instead.

You can observe the difference in output here: https://codepen.io/birtles/pen/GYWxww
(requires Firefox Nightly)

Because we have interpolation and accumulation procedures that are not associative it seems we cannot collapse the adjacent filling animations in the general case without altering the output.

-- 
GitHub Notification of comment by birtles
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/3210#issuecomment-429726141 using your GitHub account

Received on Monday, 15 October 2018 06:41:54 UTC