Is there a design pattern for dealing with overlapping UI actions and animations? A few examples:
- Let's say I have an interactive table row that expands to reveal an extra control when the user clicks on it, and vice versa if they click on it again. If the user clicks on the row while the expand animation is in progress, I want it to stop animating and shrink back to its original size from the place where it stopped.
- I have a set of rows from example 1 inside a group. When the last row is deleted, I want the group to shrink and then remove itself in a callback. However, while the group is shrinking, the user could still add another row to it. If that happens, I want the group to stop shrinking, expand to the correct size, and add the row.
- What if I have a sorting function for the UI in example 2? If the user clicks on the sort button, the rows shuffle and animate into their new positions. However, I still want the user to be able to click on table rows, delete rows, and do everything else that my UI allows. The UI should behave sensibly, with the shuffle animation taking precedence.
You could automatically fast-forward all animations for a UI element when a new action is performed on it, and/or block user input in all animating UI elements. But that seems inelegant to me. As a user, I really admire UIs that don't stutter or block me from doing what I want — UIs, in other words, that behave like real objects — and I'd like to do the same in my own applications.
It seems to me that you'd need to have constraints for each action/animation in order to make this a robust system. For example, you might need to ensure that any changes to your data/state/model happen only in the callback to your animation, not before. In example 2, you can't delete the group in your model as soon as the user clicks the button to delete the last row; it has to be done at the end of the animation, and at the end of the animation only. (Otherwise, if the user decides to add another row during the delete animation, reverting the group delete would be very difficult.) You might also need to design the animation system in such a way that if two animations from two different actions overlap, any mutually exclusive properties would continue animating as before. (In other words, if the user sorts the table while a row is expanding on click, the width expansion should not stop just because the row is moving to its new position.) And what about starting defaults for each animation? If a row fades out when deleted, and then another action implicitly cancels the delete, the fade out should stop and animate back in, even though the other action might not necessarily know about it.
There's also the higher-level issue of animations (visual property changes) vs. actions (sequences of events, with potential model changes, that include animations). In example 2, "delete last row" is clearly an action, since the end result is that a group gets removed. However, in most UI libraries, you'd add the callback to the animation, conflating the two. This is also an issue in games. If I need to trigger a grenade throw after my character's throw animation finishes, how do I represent that in my code? How do I place the grenade throw on my timeline without mixing model and view code, while still allowing the animation to slow down and speed up based on external factors?
The more I think about this, the more my head hurts, and the more I'm convinced that somebody has already thought this through a lot more carefully than me. Any ideas?
(At the moment, I'm mostly interested in this for web UI, but I'm also interested in a general approach for my future, non-web projects.)