animations
See original GitHub issueBeen thinking about how to do animations a lil, and think having a component with Nanomorph would be the right place to add them. We should be able to do stuff like this at 60fps:
This is my current state of thinking; I’m not entirely sure how to approach this yet but I think laying out the constraints can help us with figuring out how to approach this. Apologies if I’m rambling a lil here, figured it’d be better to share notes & thoughts in the open, even if they’re not as polished as they could be.
Constraints
There’s a few things I think would be cool if we could pull off:
- animate individual components
- create complex full page animations with multiple components
- have it run smoothly at 60fps
- try and keep the new terminology and APIs introduced to a minimum
- find a way to allow this to work for modals and (infinite) list elements too
Component lifecycle events
Nanomorph takes care pretty well of rendering things on a screen. If you want to add an element, it’ll take care of it with little trouble. However, it can’t pause element mutation, which is crucial for animations. For example, say an element has a fade-in effect when it’s first rendered. If we re-render midway through the animation it’ll look weird. So we want to make sure the animation completes before any further mutations occur. Stuff like a fade-out before a component is removed isn’t even possible right now, so we should allow for that.
Types of events
There’s generally 3 types of events that happen¹:
- component will be rendered for the first time
- component will be rendered, but it’s not the first time
- component will be removed from the DOM
Each of these events should be handed a lock that can be cleared using a callback. E.g. somewhat similar to:
component.on('rendered-for-the-first-time', function (done) {
this.component.classList.add('my-500-ms-transition')
setTimeout(done, 500) // tell we're done animating, allowing mutations to trigger again
})
While an animation is in progress, other animations and Nanomorph mutations should be put on hold.
The leave event
Because we just diff DOM nodes and don’t do extra lifecycle tricks, we can’t prevent elements from being unmounted. This means we can’t trigger any animation when an element is removed from the DOM. To counter this we could, however, leave an element in the DOM that can instrument between its child elements and trigger hooks on each of them.
I think the approach might be to have some form of scheduler live in the DOM, that doesn’t get unmounted so it can have its own Nanomorph instance. It can then do fine grained scheduling for its child elements, exposing all sorts of events².
Instrumenting Nanomorph through locks
As mentioned above, we would hand animation handlers a lock which they can free at the end of an animation. This is so we can make sure animations complete and don’t conflict. E.g. imagine this scenario:
- element is rendered for the first time, triggers the “enter” animation
- element is marked to be removed from the DOM, triggers the “leave” animation
In this case we’d want the “enter” animation to complete, and only then mark the element to be removed from the DOM. Now, we can’t immediately unmount the element - we need the animation to complete first. So we need a way to prevent the element from being removed.
Maintaining 60fps
ideally we’d be able to schedule the animations to the best of our abilities. E.g. not smush them all together in the same frame - but perhaps even better: schedule them when there’s free time available. Something interesting to experiment be would be to find a midway: try and run the animation with window.requestIdleCallback()
but run it anyway if it takes longer than 10ms or something. If every component would employ something like this, then things should run relatively smoothly (is my guess). Worth experimenting with at least haha.
Instrumentation
So far we’ve only talked about how to allow transitions to work with nanomorph. Sometimes we’ll need to instrument full page animations³. In react-f1 there’s a distinction between individual element renders, and full page instrumentation. Another good example of this would be jam3.com.
The way f1
/ react-f1
does it is by declaring a finite state machine, and using a pathfinding algorithm to navigate between the nodes. If you do several layers of this, you can create pretty wild animations. An example of a pathfinding algorithm is jkstra for Dijkstra / A* pathfinding.
Unanswered questions
A tricky thing with Nanomorph is that we can’t have multiple on-load
handlers on any given node so we’re slightly constrained in that regard. But there’s a few more things I think we should try to figure out:
- is it possible to have a “pure” scheduler - e.g. one where we don’t need to mount any wrapper components on the DOM?
- What does a application level scheduler look like, and how does it interact with choo’s events?
- How can we integrate elements like infinite lists and modals with animations and application level schedulers?
Wrapping up
I hope this somewhat explains the constraints my thoughts on the matter, and helps start a discussion on how to best approach animations. Thanks heaps for making it this far, and keen to hear your thoughts!
See Also
edit: meant nanomorph, not morphdom haha
Issue Analytics
- State:
- Created 6 years ago
- Reactions:7
- Comments:11 (7 by maintainers)
Top GitHub Comments
Oh by the way, updates!
In fact you can manipulate css animations from javascript, so you could include them in the app state, but, IMO, things can get a litle hacky from here.
Links