David Khourshid
@davidkpiano
Functional animation is subtle animation that has a clear, logical purpose. It reduces cognitive load, prevents change blindness and establish a better recall in spatial relationships.
But there is one more thing.
Animation brings user interface to life.
Android
First Last Invert Play
First Last Invert Play
node.getBoundingClientRect()
ClientRect {
top: 150,
bottom: 688,
left: 0,
right: 820,
width: 650,
height: 450
}
ClientRect {
top: 10,
left: 10
}
ClientRect {
top: 200,
left: 300
}
Δy: 200 - 10 = 190(px)
Δx: 300 - 10 = 290(px)
Normal Transition
ClientRect {
top: 10,
left: 10
}
ClientRect {
top: 200,
left: 300
}
Δy: 10 - 200 = -190(px)
Δx: 10 - 300 = -290(px)
FLIP Transition
.box {
transform:
translateX(-290px)
translateY(-190px);
}
FLIP Transition
.box {
transform:
translateX(0)
translateY(0);
}
ClientRect {
top: 10,
left: 10
}
FIRST: Get the initial position/size
ClientRect {
top: 200,
left: 300
}
LAST: Get the final position/size
INVERT: Transform the final node to the first position/size
.final-box {
transform:
translateX(-290px)
translateY(-190px);
}
This happens before the browser renders the final node at its resting position.
PLAY: Animate the final node to its resting position
.final-box {
transform:
translateX(0)
translateY(0);
}
Use your favorite animation library to do this!
Dynamic animations better suited for JS
Don't know positions ahead of time
// FIRST
const firstNode = document.querySelector(...);
const firstRect = firstNode.getBoundingClientRect();
// FIRST
const firstNode = document.querySelector(...);
const firstRect = firstNode.getBoundingClientRect();
// Execute change that
// introduces new view
doSomething();
// LAST
const lastNode = document.querySelector(...);
const lastRect = lastNode.getBoundingClientRect();
// FIRST
const firstNode = document.querySelector(...);
const firstRect = firstNode.getBoundingClientRect();
// Execute change that
// introduces new view
doSomething();
// LAST
const lastNode = document.querySelector(...);
const lastRect = lastNode.getBoundingClientRect();
// INVERT
const deltaX = firstRect.left - lastRect.left;
const deltaY = firstRect.top - lastRect.top;
// FIRST
// ...
// LAST
// ...
// INVERT
const deltaX = firstRect.left - lastRect.left;
const deltaY = firstRect.top - lastRect.top;
// PLAY
lastRect.animate([
{
transform: `
translateX(${deltaX}px)
translateY(${deltaY}px)
`
},
{
transform: `
translateX(0)
translateY(0)
`
}
], {
duration: 300, // milliseconds
easing: 'ease-in-out',
fill: 'both'
});
inverted position
initial position
position: rect.top, rect.left
scale: rect.width, rect.height
color: getComputedStyle()
rotation: have fun
size: 😐
Must be nested inside a <div>
.final-box {
transform: translateY(100px);
}
Δy: lastRect.height - firstRect.height
Δy
.final-box {
transform: translateY(100px);
}
.outer-box {
transform: translateY(-100px);
}
-Δy
.final-box {
transform: translateY(100px);
}
.outer-box {
transform: translateY(-100px);
overflow: hidden;
}
.final-box {
transform: translateY(0);
}
.outer-box {
transform: translateY(0);
overflow: hidden;
}
TIPS:
STRATEGY:
// INVERT
const deltaX = firstRect.left - lastRect.left;
const deltaY = firstRect.top - lastRect.top;
// PLAY
innerRect.animate([
{
transform: `translateX(${deltaX}px)`
},
{
transform: `translateX(0)`
}
], {
duration: 300, // milliseconds
easing: 'ease-in',
fill: 'both'
});
outerRect.animate([
{
transform: `translateY(${deltaY}px)`
},
{
transform: `translateY(0)`
}
], {
duration: 300, // milliseconds
easing: 'linear',
fill: 'both'
});
let flip = new FLIP({
element: target,
duration: 2000
});
// First position & opacity.
flip.first();
// Apply the 'end' class and
// snapshot the last position & opacity.
flip.last('end');
// Move and fade the element
// back to the original position.
flip.invert();
// Play it forwards.
flip.play();
import FlipMove from 'react-flip-move';
class TopArticles extends Component {
renderTopArticles() {
return this.props.articles
.map(article =>
<Article
{...article}
key={article.id}
/>
);
}
render() {
return (
<div className="top-articles">
<FlipMove easing="...">
{ this.renderTopArticles() }
</FlipMove>
</div>
);
}
}
Why are we OK with the user feeling like they are being teleported every time the page changes?
Inspiration Resources: