Creating Primitive Motion Design System Hooks Using Framer Motion
Michael Mangialardi
Posted on December 28, 2021
Design systems have been recognized as extremely important for harmonizing the look, feel, and behavior of various applications within a company.
However, we don't always see such an organized system for stitching together consistent, meaningful animations.
Quite simply, we should consider bringing motion into our design systems.
The Difficulty
Bringing motion into a design system isn't as easy as it sounds for the following reasons:
Managing a design system without motion is enough work in and of itself.
Orchestrating motions is a unique skillset.
Without motion expertise, it's a bit easier to say what we don't want instead of what we do want.
Design specifications can be grouped by component. It's harder to generalize motion "groups" as it may vary depending on the context.
In a word, it's not always obvious how to generalize animations across a stack of applications, and even if some trends are obvious, it can be time-consuming.
A Possible Solution
A potential solution for reducing the cognitive load of managing a motion design system is to focus on the primitives.
Instead of trying to generalize animations for shared contexts across various applications, focus on organizing the primitives of a motion design system.
What are the primitives?
The primitives of motion design system are:
- Animation types
- Easings and durations based on type
- Basic motions
By defining these primitives, we can organize them into a system and expose assets to apply that system in code.
Animation Types
Generally, you can group an animation into 3 types:
- Entrance - Animating an object when it enters
- Exit - Animating an object when it exits
- Effect - Animating an object that has already entered but is not exiting
// motion.js
const types = {
entrance: 'entrance',
exit: 'exit',
effect: 'effect',
};
Easings and Durations
Duration refers to how long it takes to animate a property from point a to point b (i.e. 200ms, 250ms, 500ms).
// motion.js
const types = {
entrance: 'entrance',
exit: 'exit',
effect: 'effect',
};
const durations = {
fast: 200,
slow: 250,
};
Easing refers to where you animate most of the property in the animation's timeline (from point a to point b).
easeOut
is an easing function used primarily for entrance (the opposite of "out") animations.
It animates most of the property of the its way "out".
easeIn
is an easing function used primarily for exit (the opposite of "in") animations.
It animates most of the property of the its way "in".
easeInOut
is an easing function that animates most of the property in the middle of the timeline.
It is used primarily for animating something that is neither entering nor exiting.
// motion.js
const types = {
entrance: 'entrance',
exit: 'exit',
effect: 'effect',
};
const durations = {
fast: 200,
slow: 250,
};
const easings = {
effect: 'easeInOut',
entrance: 'easeOut',
exit: 'easeIn',
};
Putting it all together, we can map a duration and easing function to a motion type:
// motion.js
const types = {
entrance: 'entrance',
exit: 'exit',
effect: 'effect',
};
const durations = {
effect: 250,
entrance: 250,
exit: 250,
};
const easings = {
effect: 'easeInOut',
entrance: 'easeOut',
exit: 'easeIn',
};
const transitions = {
effect: {
duration: durations[types.effect],
ease: easings[types.effect],
},
entrance: {
duration: durations[types.entrance],
ease: easings[types.entrance],
},
exit: {
duration: durations[types.exit],
ease: easings[types.exit],
},
};
Basic Motions
Finally, we can call out common, basic types of motions and map to their transition
type:
// motion.js
const types = {
entrance: 'entrance',
exit: 'exit',
effect: 'effect',
};
const durations = {
effect: 250,
entrance: 250,
exit: 250,
};
const easings = {
effect: 'easeInOut',
entrance: 'easeOut',
exit: 'easeIn',
};
const transitions = {
effect: {
duration: durations[types.effect],
ease: easings[types.effect],
},
entrance: {
duration: durations[types.entrance],
ease: easings[types.entrance],
},
exit: {
duration: durations[types.exit],
ease: easings[types.exit],
},
};
const motions = {
move: { transition: transitions[types.effect] },
moveIn: { transition: transitions[types.entrance] },
moveOut: { transition: transitions[types.exit] },
// ...etc
};
Exposing the Motions Via Hooks (Using Framer Motion)
Once the basic motions have been grouped by type, and the types have been mapped to a common duration and ease, we can export these to work with specific technologies/frameworks.
Here's an example if of exposing a basic motion hook by wrapping Framer Motion:
// motion.js
import { motion, useAnimation } from 'framer-motion';
// ...
function toSeconds({ ms }) {
return ms / 1000;
}
function normalize(transition) {
return {
...transition,
duration: toSeconds({ ms: transition.duration }),
};
}
export function useMove(config = {}) {
const controls = useAnimation();
return {
motion,
animate: controls,
trigger: (animatedProperties = {}) => {
controls.start({
...animatedProperties,
transition: normalize(transitions.move),
});
},
};
};
// SomeComponent.jsx
import React, { useState } from 'react';
import { useMove } from '...';
const SomeComponent = () => {
const [isShifted, setIsShifted] = useState();
const { motion, animate, trigger } = useMove();
return (
<motion.div
animate={animate}
onClick={() => {
trigger({ x: isShifted ? 0 : 100 });
setIsShifted(!isShifted);
}}
>
Click me!
</motion.div>
);
};
🎉 Tada! We've established a way to organize our motions in code.
Now, you may want to approach this quite differently, but my hope is that this gets the conversation going.
Posted on December 28, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.