How do you approach designing and implementing complex UI interaction animations?
(I'm not talking about specific languages and libraries like jQuery or UIKit, unless they force you into specific way of thinking about managing interdependent animations, which I'm interested in.)
Consider a deceptively “simple” task like designing and programming iOS home screen.
However, the magnitude of hidden complexity is astounding.
Just a few things I noticed about the interface:
I'm sure there is even more complexity here that I failed to notice.
In rough generalization, for each pair of (animation, user_action)
in the same interface context you need to decide what if user_action
happens while animation
is already running.
In most cases, you can
But then there may be several actions during the animation, and you have to decide which actions to discard, which to queue, and whether to execute all queued actions, or just the last one, when the animation is over.
If something is queued when animation finishes, and the animation is changed, you need to decide if queued actions still make sense, or need to be removed.
If this sounds too theoretical, consider a real-world example: how do you deal with the user dragging an app downwards, waiting for rearrangement to begin, then immediately dragging the app back upwards and releasing it? How do you ensure the animation is smooth and believable in every possible case?
I find myself unable to keep even half of the possible scenarios in the head. As expressiveness of the UI increases, the number of possible states begins to violently violate the 7±2 rule.
My question, therefore, is as follows:
How do you tame the complexity in designing and implementing animations?
I'm interested both in finding effective ways of thinking about the problem, as well as means to solve it.
As an example, events and observers proved to be a very effective abstraction for most UIs.
But can you design and implement an iOS-like drag-n-drop screen relying on events as the main abstraction?
How tangled does the code have to be to accurately represent all possible states of the UI? Would it be an event handler adding another event handler when some boolean variable is true to the function that sets it to false, unless yet another event handler ran before it?
“Have you never heard of classes?” you may wonder. Why, I have, but there is just too much state that these classes will want to share.
To sum up, I'm looking for language-agnostic (although probably language-or-framework-inspired) techniques for managing complex interdependent, cancelable, animations happening in sequence or at once, and describing how they react to user actions.
(All of this considering I don't have to program animations itself—i.e., I do have access to a framework like jQuery or Core Animation that can animate(styles, callback)
the thing for me, and I can cancel
it.)
Data structures, design patterns, DSLs are all good if they help to the problem.
In the described problem, there is an implicit notion of system state. Animations are stateful, and therefore any composition of them is just as stateful, and arguably even more so.
One way to think about states and actions would be Finite-state machines.
Read this article explaining how to apply FSM to implement a custom tooltip in JavaScript:
This example may seem slightly convoluted but does illustrate the point: finite-state machine force you to think what states are possible, which transitions between them are valid, when they should be triggered and what code should be executed once they are.
Because IBM's article forbids usage of its code sample, I encourage you to also read an article by Ben Nadel that uses FSM to implement a drop-down menu widget.
Ben writes:
I've seen the way that Finite State Machines can take a large, complex tasks and break it down into smaller, much more manageable states. I've even tried to apply this kind of mentality to binding and unbinding event handlers in JavaScript. But, now that I am getting more comfortable with state machines and, in particular, state transitions, I wanted to try and apply this mindset to an cohesive user interface (UI) widget.
Here's a slightly stripped down version of his code:
var inDefault = {
description: "I am the state in which only the menu header appears.",
setup: function() {
dom.menu.mouseenter(inHover.gotoState);
},
teardown: function() {
dom.menu.unbind("mouseenter");
}
};
var inHover = {
description: "I am the state in which the user has moused-over the header of the menu, but the menu has now shown yet.",
setup: function() {
dom.menu.addClass("menuInHover");
dom.menu.mouseleave(inDefault.gotoState);
dom.header.click(
function(event) {
event.preventDefault();
gotoState(inActive);
}
);
},
teardown: function() {
dom.menu.removeClass("menuInHover");
dom.menu.unbind("mouseleave");
dom.header.unbind("click");
}
};
var inActive = {
description: "I am the state in which the user has clicked on the menu and the menu items have been shown. At this point, menu items can be clicked for fun and profit.",
setup: function() {
dom.menu.addClass("menuInActive");
dom.stage.mousedown(
function(event) {
var target = $(event.target);
if (!target.closest("div.menu").length) {
gotoState(inDefault);
}
}
);
dom.header.click(
function(event) {
event.preventDefault();
gotoState(inHover);
}
);
dom.items.delegate(
"li.item",
"click",
function(event) {
console.log(
"Clicked:",
$.trim($(this).text())
);
}
);
},
teardown: function() {
dom.menu.removeClass("menuInActive");
dom.stage.unbind("mousedown", inDefault.gotoState);
dom.header.unbind("click");
dom.items.undelegate("li.item", "click");
}
};
Note that event handlers are bound when entering a state and unbound when leaving this state.
The biggest advantage FSMs give in solving this problem is they make the state explicit.
While each animation may contribute to the state of encompassing system, your system can never be in two states at once or no states at all, and debugging becomes almost trivial because you always get to see what state the system (or each subsystem) in, given your state design makes sense.
Also, by forcing you into designing states explicitly, using FSMs rules out the possibility that you don't think of a particular combination of state/actions. There is no “undefined behavior” because every transition is explicit and is part of your FSM design.
If you've read this far, you might be interested in Additive Animations (another intro). They are now default in iOS 8 and have been advocated by Kevin Doughty for several years now.
This is a different approach, where, while keeping the system stateful, you allow several (even opposite) animations to be active at the same time. This can give you crazy results but it's an interesting concept.
The main idea is to avoid defining animation as something going from absolute value A to absolute value B, and instead define animations as relative to their final value (each animation going from -Delta to 0). This allows you to combine several animations seamlessly by summing their relative values at each point of time, and avoid spikes caused by reversals or cancellations:
(source: ronnqvi.st)
For barebone framework-agnostic example of additive animations, check out alexkuz's additive-animation module (demo).
If you've read this far, you must be really interested in animations! Currently, I'm intrigued by react-state-stream approach. It proposes to express animations as lazy sequences of states. This opens up a lot of possibilities such as expressing infinite animations, gradually adding and removing transforms from animations, et cetera.
If you want to read one article on animation, I suggest this be Thoughts on Animation by Cheng Lou.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With