Understanding liquid-fire
by @ianstarz
Denver Ember.js meetup 7-29-15
First, a few thoughts on design...
https://www.youtube.com/watch?v=vq_BcIFM8Rc
(before the video plays)
Here is a clip of Edward Faulkener at the Silicon Valley Ember.js meet up in April.
(after the video plays)
Edward's talk at the Silicon Valley Ember.js meet up is an excellent resource for liquid-fire, and I got a lot out of it on my second time watching it. If you haven't seen this talk or the one he gave at Ember Conf, I'd highly recommend it as he does a lot of cool stuff with liquid-fire and other aspects of Ember CLI.
But let's talk for a moment about placing our designs in context.
Take for instance this product. Does anyone know what it is? Admittedly, this is actually a conceptual design for Kashi Go Lean Crunch cereal, so this example is somewhat contrived, but it was designed nonetheless. Perhaps we can make sense of this design in context.
In the very specific building and task for grocery shopping, and even more specifically, in the cereal aisle, we have pretty standardized expectations for what the products will look like.
Now, a product like this...
..in this environment, might do one of three things: 1) it might stand out to you; 2) it might blend in naturally; 3) or it might offend you. Each one of these outcomes has a variety of different consequences that should be considered, but ultimately, you want to start with one simple consideration: what effects does this physical object have in its environment.
The way we perceive the world is a natural process. Our brains have evolved to quickly and efficiently place objects in the context of our world. This is why good design is so often "invisible". Good design is frictionless when being placed in our surroundings.
Contrarily, unfunctional design can be jarring. It calls attention to itself, and away from the purpose it is trying to fit. The lines and composition might be pleasant, but the object's function in the physical world can be quixotic.
https://www.youtube.com/watch?v=vq_BcIFM8Rc
(before video plays)
Here is another clip of Edward talking about abstraction and it's cognitive load on the brain.
(after video plays)
Edward is trying to convey two points here. One, the motion design we're interested in is animation that helps the user understand how they affect the state of the application. And two, that even for abstract thinkers, using this motion design helps users consume abstraction.
http://www.selflanguage.org/_static/published/animation.pdf
Here is an excerpt from a 1993 paper by Standford professor Bay-Wei Chang and Sun Microsystems engineer David Ungar titled "Animation: From Cartoons to the User Interface". In the abstract they state "When the user cannot visually track the changes occurring in the interface, the causal connection between the old state of the screen and the new state of the screen is not immediately clear. How are the objects now on the screen related to the ones which were there a moment ago? Are they the same objects, or have they been replaced by different objects? What changes are directly related to the user’s actions, and which are incidental? To be able to efficiently and reliably interpret what has happened when the screen changes state, the user must be prepared with an expectation of what the screen will look like after the action. In the case of most interactions in unanimated interfaces, this expectation can only come by experience; little in the interface or the action gives the user a clue about what will happen, what is happening, or what just happened."
http://www.smashingmagazine.com/2013/10/smart-transitions-in-user-experience-design/
We encounter these ideas in user experience and user interface design, and even though we as user experience designers understand why something might behave like this.
http://www.smashingmagazine.com/2013/10/smart-transitions-in-user-experience-design/
When we simulate a physical experience, the experience is easier to understand and actually takes away cognitive load, even though you are adding to the experience. There are many more frames between the animated scroll vs the non animated scroll, but they help the user instead of making them think harder. This should be a motivation as motion designers. We should ask ourselves: how does this motion help decrease the cognitive load on our users?
Now this doesn't come without more work. Motion design is not easy, and it often needs to be thought of during the design process, which adds more work in the mock phase.
https://medium.com/@pasql/transitional-interfaces-926eb80d64e3
But showing this to a client or stakeholder...
https://medium.com/@pasql/transitional-interfaces-926eb80d64e3
...does not have the same affect as does showing this,
https://medium.com/@pasql/transitional-interfaces-926eb80d64e3
or this.
Not all fluff
Motion design can go beyond taking the cognitive load off of the user. Motion design can nudge the user experience in a particular direction.
http://www.smashingmagazine.com/2013/10/smart-transitions-in-user-experience-design/
A stateful toggle invisibly reminds us how to get back to the previous state.
http://www.smashingmagazine.com/2013/10/smart-transitions-in-user-experience-design/
Context based hiding can draw focus to what's important.
http://www.smashingmagazine.com/2013/10/smart-transitions-in-user-experience-design/
Or gently invite the user into functionality. Here we see the comment form is easy and simple, so we may be more inclined to comment. Once we get into the flow and see there are two more fields, we may not mind as much since we're already in the flow. Where as seeing the three fields to begin with may be perceived as too much effort to leave a comment.
http://www.smashingmagazine.com/2013/10/smart-transitions-in-user-experience-design/
Here we might continue to read, because we are persuaded by motion techniques that convey transition and progression.
Let's not get too carried away though.
http://valhead.com/2013/11/25/what-are-your-transitions-saying/
Take this unnecessary animation from Apple. The addtional "sugar" they ease in and out with is especially distracting. Like the case of our conceptual cereal box, this physical interaction feels slightly out of place.
http://www.beyondkinetic.com/motion-ui-design-principles/
In the game of Dots, however, the playfulness of the motion is warranted by the physical space it occupies, and the motion design has a common thread from the way we interact with the menus to the way we play the game.
Material Design
Thinking about simulating the physical world, naturally leads into material design, which is guided by 3 principles.
Material is metaphor
Material as a metaphor is easy for us to understand. We use metaphors all the time in computer science. For example, "bugs" or coding "closer to the metal" are metaphors that help anchor abstract ideas in the world of the physical.
Bold, graphic, intentional
I believe this principle to be more subjective, and open to interpretation. Take it or leave it, but this is not what we're interested in right now.
Motion provides meaning
This is what we're interested in. Motion provides meaning is the 3rd principle of Material Design, and it falls in line with the philosophies of liquid fire.
The meanings...
Rachel Nabors provides us with some examples of this meaning in her article on A List Apart.
Causality
http://alistapart.com/article/web-animation-at-work
Here we see the meaning of causality. When a user affects something, and they feel they've affected something, it's because the motion happened after their interaction and not before. When it happens before, the system seems to "act on its own".
Feedback
http://alistapart.com/article/web-animation-at-work
Feedback provides the user with a sense that they have affected something. This way, if the user experience hangs, the user can suspect the system is hanging by something other than whether or not they actually pressed the button.
Progression
http://alistapart.com/article/web-animation-at-work
This is the base principle for animated spinners, but can be applied in many more creative ways. Progression is often used to keep users engaged while they are not necessarily affecting the system, but could also be used to show how incremental actions affect the system.
Relationships
http://alistapart.com/article/web-animation-at-work
One of the most important meanings behind motion is spatial relationship, which can help orient the user within their viewport. When we feel like the things we are seeing through the viewport are a part of a larger connected system, then when we move through the system we don't feel disjointed.
How do we do this?
There of course are many ways to animate things on the web. Dozens of animation libraries that vary from using canvas, to SVG, to WebGL, that all provide a variety of purposes.
How do we do this in Ember with liquid-fire?
Use motion design to understand application state changes
To implement motion design in a way that plays nice with Ember
Taking the principles we just covered we can more concretely define the goals of liquid-fire which are: 1) To use animation specifically to provide the user with natural information on how they are affecting the application state, and 2) to do so in a way that goes with the flow of the framework instead of fighting it.
Animations in Ember without liquid-fire
To appreciate what liquid-fire does for us, let's first think about how we could animate without it. Also, because Ember prescribes storing the state of your application in the URL, we'll especially be interested in animating between routes.
/circle
For instance, we have a route called "circle"...
/square
...and a route called "square", and we move between these two routes.
Without animation
Without animation, Ember's architecture goes through the following sequence to transition between the two routes.
/circle
When we leave the circle route, Ember's router will begin the transition...
/circle
...which involves tearing down the current route...
/square
...representing the new route hierarchy to the rendering layer...
/square
...which the rendering layer then populates the route.
Steps needed to animate
Route knows it's deactivating (exiting the circle route)
Router determines which route we're going to (the sqaure route)
Service we implement saves the view we're about to tear down.
Tear down route, but allow service to keep copy of the view in the outlet
Render the new route, but hide and position the new element (the square)
Animate the old value (the circle) off the screen to the left
Animate the new value (the square) onto the screen from the right
Tear down the old value (remove the circle from the DOM)
This is all fine and dandy, until we want to do a simple sliding transition. To animate the transition. We need both of the routes, both of the views available during the duration of the transition. In this case the steps to animate might look like this.
(Step through list)
Woah, that's alot, and worse yet it is concerning our Router, Routes, and View objects all with how things should animate. That's a lot of work! And it feels like we're fighting the framework.
The main principles of liquid-fire
Use template helpers to keep track of old and new values
Defer to a transition map when asking should we animate, and if so, how?
This brings us to the two main principles of liquid-fire, which are these. (step through principles) All of the complexity of animating between state changes is now handled by liquid-fire by minimal impact to your templates and by introducing a new function (the transition map) into your application. You won't need to touch your router, routes, or views to make any of this work!
Template helpers
Let's investigate these two principles in detail. The tempalte helpers are responsible for two things: 1) Saving the new and the old values when something changes, 2) adding some layout to help with animation.
matching routes
// app/transitions.js
export default function() {
this.transition(
this.fromRoute('circle'),
this.toRoute('square'),
this.use('toLeft')
);
}
Now this only goes in one direction.
matching routes
// app/transitions.js
export default function() {
this.transition(
this.fromRoute('circle'),
this.toRoute('square'),
this.use('toLeft')
);
this.transition(
this.fromRoute('square'),
this.toRoute('circle'),
this.use('toRight')
);
}
To go in the other direction, too, you could do this.
matching routes
// app/transitions.js
export default function() {
this.transition(
this.fromRoute('circle'),
this.toRoute('square'),
this.use('toLeft'),
this.reverse('toRight')
);
}
But this is so common that you can just do this.
More examples on the liquid-fire site...
Understanding liquid-fire
by @ianstarz
Denver Ember.js meetup 7-29-15