-
Notifications
You must be signed in to change notification settings - Fork 3
Consideration for Animation
One of the primary goals of this project is to make it dead simple to author and publish modern, beautiful user experiences. Obviously, this must include the ability to visually transition elements from one state to another.
Like components themselves, animations will be built up from a set of core concepts into higher level building blocks.
Transitions are built on top of the Traits system and can be:
- Composed to create sophisticated animations
- Assigned to individual or groups of components using component attributes or Trait selectors
- Interrupted or cancelled at any time
Animation introduces some unique challenges related to functional component declarations. In order to execute animations, we need to retain the animation state over time and ensure that we apply it to component instances, even as those instances are potentially replaced by recomposing the tree. Fundamentally, we like to conceive of the component tree as simply derived state from our declarations, but animations have lots of implicit and hidden state that is made worse by being inherently about changing state over time.
Some challenges that immediately come to mind are:
Components need to become reliably addressable from their declarations. With the current, naive implementation of auto-generated component IDs (for those components that don't have an explicitly configured ID), we get a new ID every time composition functions are executed. With the initial approach to animation, this means animations begin again every time we recompose any targeted part of the tree.
I'm not sure exactly how to create a canonical address for a component other than to require a "Key" or "ID" field. ID is tough because it implies global (to some extent, presumably at least application wide) scope. It's no fun as a developer, to get stymied by seemingly irrelevant global uniqueness constraints when just trying to get something working. I'd love it if we could just hash component configurations and use that, but the configurations can and should change from one composition execution to another. This is specifically how we change state in the application. Given these issues, I'm leaning toward the following:
- Remove auto-generated IDs: These are broken unless they can be created once and tied to regenerated component instances.
- Introduce a component Key attribute. Key could be auto-assigned with some aggregation of infrequently changing fields (e.g., [Type]-[index]). Path() might fall back to Keys if explicit IDs aren't found.
- Associate a field-assigned trait with a given Path()
Where do we store animation state? We cannot store it on random component instances, because they're likely to be reconstructed on a regular or arbitrary basis. We can store state in Root/Window or possibly in the Builder. I'm really enjoying at the moment how Builder was becoming a dumping ground and then got completely cleaned up to basically hold nothing between composition calls. That's not a great reason, but it makes me want to start by putting state into or onto the Root component. Whomever owns the EnterFrame triggering.
Oh events. I'm working hard to do this as a callback for now, because I want to see transitions and animations before I dig into events. But even providing a simple callback presents a number of challenges. I need to resubscribe each time the tree is rebuilt, but not more often. I need to drop previous subscriptions when the tree is torn down, but we don't currently have any facility for that.
Some things I do not want to see are:
- Deep tree traversal for "Removed" event broadcasting
- Dangling references to entirely unused component trees because of failure to clean up callbacks
I'm considering the following:
- Introduce a shallow (single level) notification when any given component is removed (OnRemoved).
- Subscribe to OnEnterFrame on any component
- In the OnEnterFrame implementation, subscribe to OnRemoved in each and every parent. If any of these are received, clean up the OnEnterFrame subscription, which should remove all of the others.
...to be continued.