Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are good abstractions for complex animations?

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.

iOS home screen

However, the magnitude of hidden complexity is astounding.
Just a few things I noticed about the interface:

  • When you barely touch an icon, its opacity changes but the size change is delayed.
  • If you drag an app between two other apps, there is a noticable delay before all apps rearrange to move the free space. So if you just keep moving an app across the screen, nothing happens until you settle.
  • Rearrangement happens line-by-line, first goes the line you hovered over, and it triggers the next line in chain, to the line where the free space previously was.
  • If you drop an app, it will drop at the now-free space, and not just where you dropped it.
  • If you hover an app over another app, radial light will appear, blink twice, and only then a group will be created.
  • If the group was created right to the free space, and then discarded, it will animate left to occupy the free space while discarding.

I'm sure there is even more complexity here that I failed to notice.

Continuous Animations vs Discrete Actions

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

  • Cancel the animation;
  • Change the animation on the go;
  • Ignore the action;
  • Queue the action to when the animation finishes.

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?

Right Tools for the Job

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.

like image 840
Dan Abramov Avatar asked Jun 01 '12 15:06

Dan Abramov


1 Answers

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:

enter image description here

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:

additive animations
(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.

like image 176
Dan Abramov Avatar answered Nov 19 '22 13:11

Dan Abramov