[csswg-drafts] [web-animations] proposal idea: `ObjectKeyframeEffect` and `Animator` API, allowing animation of JS objects besides DOM element styles. (#9974)

trusktr has just created a new issue for https://github.com/w3c/csswg-drafts:

== [web-animations] proposal idea: `ObjectKeyframeEffect` and `Animator` API, allowing animation of JS objects besides DOM element styles. ==
One aspect of Web Animations that is missing, and that requires people to abandon the idea of using Web Animations in favor of JavaScript libraries like [Tween.js](https://github.com/tweenjs/tween.js), is that the current design allows animating only DOM elements (unless cumbersome and non-ideal workarounds are applied).

# Current problem:

Suppose we are creating WebGL-powered graphics using JavaScript. The JavaScript objects are not DOM elements, and we want to animate them.

This does not work:

```js
const someObject = {
  position: {x: 0, y: 0, z: 0} // Suppose this represents the position of an object in 3D space.
}

const keyframes = new KeyframeEffect(someObject.position, [{x: 0}, {x: 200}], 3000)
const animation = new Animation(keyframes, document.timeline)

animation.play()

// render the WebGL content while the animation is playing
requestAnimationFrame(function loop() {
  renderScene(someObject, camera) // hypothetically render the scene 
  if (animation.playState !== 'finished') requestAnimationFrame(loop)
})
```

# Current workarounds

Here are the current workarounds:

1. Don't use Web Animations API, instead use [Tween.js](https://github.com/tweenjs/tween.js) for example. This workaround is not ideal because we want to embrace what the web gives us, instead of dropping it in favor of additional libraries.
   ```sh
   npm install @tweenjs/tween.js
   ```

2. Animate a hidden DOM element, and read properties from its computed style. The downsides of this are that it is cumbersome and hacky, plus incurs unnecessary performance cost due to cycling the browser's CSS engine and obtaining values from the element's computed styles.
   ```js
   const someObject = {
     position: {x: 0, y: 0, z: 0} // Suppose this represents the position of an object in 3D space.
   }
   
   const dummy = document.createElement('div')
   dummy.style.display = 'none' // hide it so the browser avoids spending resources rendering it.
   document.body.append(dummy)
   
   const keyframes = new KeyframeEffect(dummy, [{translate: '0px'}, {translate: '200px'}], 3000)
   const animation = new Animation(keyframes, document.timeline)
   
   animation.play()

   const dummyStyle = getComputedStyle(dummy)
  
   // render the WebGL content while the animation is playing
   requestAnimationFrame(function loop() {
     someObject.position.x = parseFloat(dummyStyle.translate) || 200
     console.log(someObject.position.x)
     renderScene(someObject, camera) // hypothetically render the scene
     if (animation.playState !== 'finished') requestAnimationFrame(loop)
   })
   ```
   [Live example on CodePen](https://codepen.io/trusktr/pen/vYPPLLa?editors=0010)

# Proposal 1, keyframe effects for plain JS objects

**Idea 1:**

- Make a new `StyleKeyframeEffect` class that works how `KeyframeEffect` currently works and animates the _style_ of the given DOM element. This class extends from `KeyframeEffect` for semantics. Perhaps this class should rather (or also) directly accept `CSSStyleDeclaration` instances, `CSSStyleRule` instances directly, etc, for better semantics and more possibilities (for example, animate the properties of a `CSSStyleRule` in a stylesheet and it can animate multiple elements at once!).
- Add a new `ObjectKeyframeEffect` class that extends from `KeyframeEffect`, and that accepts any JS objects, similar to what was attempted in the first example above.
  - This class behaves similarly to `StyleKeyframeEffect`, but the `target` is simply any JS object, and the properties in the `keyframes` array are plain JS number properties that correspond to the same-name properties of `target`.
- soft-deprecate `KeyframeEffect` as a class that people should instantiate directly, and make it an "abstract" class that both `StyleKeyframeEffect` and `ObjectKeyframeEffect` extend from. For backwards compatibility, `KeyframeEffect` should continue working as-is (but sites like MDN will show a warning telling people to use `StyleKeyframeEffect` or `ObjectKeyframeEffect`, etc).

The usage of `ObjectKeyframeEffect` would look like this:

```js
const someObject = {
  position: {x: 0, y: 0, z: 0} // Suppose this represents the position of an object in 3D space.
}

const keyframes = new ObjectKeyframeEffect(someObject.position, [{x: 0}, {x: 200}], 3000)
const animation = new Animation(keyframes, document.timeline)

animation.play()

// render the WebGL content while the animation is playing
requestAnimationFrame(function loop() {
  renderScene(someObject, camera) // hypothetically render the scene 
  if (animation.playState !== 'finished') requestAnimationFrame(loop)
})
```

**Idea 2:**

- Instead of multiple classes as in **Idea 1**, perhaps allow multiple types of objects to be passed into `KeyframeEffect`.
- If an `Element` is detected, it works as currently.
- If a `CSSStyleRule` is detected, it operates on the properties of the rule in a similar way as how `KeyframeEffect` currently operates on the properties of an element's style.
- etc
- Finally, if none of the above instance types match (f.e. with `instanceof`), then treat it as a plain JS object, and assume that the keyframes contain objects with same-name properties and number values.

This would work exactly like in the very first example above.

Being more explicit with specific classes also means that the disctinction can be more clear in non-browser runtimes. For example, a Node.js library, or Node-like JS runtime, could provide `ObjectKeyframeEffect` but not `StyleKeyframeEffect`. Alternatively, a non-browser environment can also accept less types of objects if using a single class (for example, accept only JS objects, but not elements because there are no elements).

# Proposal 2, `Animator`

**Idea 1**

This is a beginning exploration of an idea for animating *anything*, not just DOM elements.

A new `Animator` class would allow wrapping any types of objects, and the wrapper instance would have methods like `.animate()` that are similar to the current `Element.animate()`.

For animating plain JS objects, it could look like the following:

```js
const someObject = {
  position: {x: 0, y: 0, z: 0} // Suppose this represents the position of an object in 3D space.
}

const animator = new ObjectAnimator(someObject)
const animation = animator.animate([{x: 0}, {x: 200}], 3000)
const animation2 = animator.animate([{y: 0}, {y: 400}], 4000)

// render the WebGL content while the animation is playing
requestAnimationFrame(function loop() {
  renderScene(someObject, camera) // hypothetically render the scene 
  if (animation.playState !== 'finished' || animation2.playState !== 'finished') requestAnimationFrame(loop)
})
```

There'd also be similar `ElementAnimator` or `StyleAnimator` classes, where for example `StyleAnimator` could accept a `CSSStyleRule` or similar.

**Idea 2**

Similar to with Proposal 1, maybe instead of having multiple distinct classes for styles, objects, etc, a single `Animator` class can accept different types of objects and act accordingly.

Being more explicit with specific classes also means that the disctinction can be more clear in non-browser runtimes. For example, a Node.js library, or Node-like JS runtime, could provide `ObjectAnimator` but not `StyleAnimator`. Alternatively, a non-browser environment could also accept less types of objects if using a single class (for example, accept only JS objects, but not elements because there are no elements).

# Summary

I think I like **Idea 2** of each of the two proposals (accepting multiple types of objects). But I'm not sure. In any case, being able to animate plain JS objects would be not only very useful in a browser, but could also help to pave some standards in non-browser JS runtimes.

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/9974 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Saturday, 17 February 2024 22:53:25 UTC