Creating Primitive Motion Design System Hooks Using Framer Motion
Tags: javascript, webdev, design, tutorial
Published: 2021-12-28 17:59:06

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](https://www.invisionapp.com/inside-design/motion-design-systems/).

![motion-design-system-framer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbbi6njonh3wbvyu1lsz.jpeg)

## The Difficulty
Bringing motion into a design system isn't as easy as it sounds for the following reasons:

1. Managing a design system without motion is enough work in and of itself.

2. Orchestrating motions is a unique skillset. 

3. Without motion expertise, it's a bit easier to say what we _don't want_ instead of what we _do want_.

4. 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:

1. Animation types
2. Easings and durations based on type
3. 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:

1. **Entrance** - Animating an object when it enters
2. **Exit** - Animating an object when it exits
3. **Effect** - Animating an object that has already entered but is not exiting

```js
// 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).

```js
// 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.

```js
// 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_:

```js
// 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:

```js
// 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.

![motion-design-system-framer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcxx1rg3limjloajyj6c.png)

Here's an example if of exposing a basic motion hook by wrapping [Framer Motion](https://www.framer.com/motion/):

```jsx
// 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>
  );
};
```

![motion-design-system-framer](https://res.cloudinary.com/dpzpn0xkz/image/upload/v1640714115/t5gdu1gprmbqwgaati4f.gif)

🎉 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.