Animation with Framer Motion

Author : JaNakh Pon , January 16, 2022

Tags

Introduction

FramerMotion is a production-ready motion library for React from Framer. It brings declarative animations, effortless layout transitions and gestures while maintaining HTML and SVG semantics.

Introduction

Motion Elements

To animate HTML and SVG elements, we can use motion component.

There's a motion component for every HTML and SVG element, for instance motion.div, motion.circle etc.

<motion.div className="flex justify-center m-5">
  <h2>Inside Motion Component</h2>
</motion.div>

Animation

We can control motion component's animations using its properties. For example,

<motion.div
  initial={{}}
  animate={{}}
  transition={{}}
  ...
  className="flex justify-center m-5"
>
  <h2>Inside Motion Component</h2>
</motion.div>

Transition

A Transition is an object which can contain Orchestration props like delay, that schedule the animation as a whole.

We can define the types of animation such as Tween, Spring or Inertia

<motion.div
  animate={{ rotate: 180 }}
  transition={{ type: 'spring' }}
  ...
  className="flex justify-center m-5"
>
  <h2>Inside Motion Component</h2>
</motion.div>

Transition Orchestration

Orchestration

We can use delay, delayChildren, staggerChildren, staggerDirection and "beforeChildren" | "afterChildren" to orchestrate animation.

staggerChildren defines how much time should pass between each child’s animation starting. when defines the point at which these animations should begin. A value of afterChildren means that the stagger delay will occur after the child animation. beforeChildren means the opposite — waiting the defined time before the animation.

The major difference here is on the first animation, whether it starts immediately (afterChildren) or waits briefly (beforeChildren). We don’t want to delay the first child’s animation, so we’ll use afterChildren.

<motion.div
  initial={{
    y: "-100",
    opacity: 0,
  }}
  animate={{
    y: 0,
    opacity: 1,
    transition: {
      duration: 0.5,
      type: "spring",
      stiffness: 60,
      when: "beforeChildren",
    },
  }}
  ...
>
    <motion.ul ... >
      {/* put individual animation to li */}
      {data.map((d,i) => <motion.li key={i}></motion.li>)}
    </motion.ul>
</motion.div>

LayoutGroup

Group motion components that should perform layout animations together.

By default, motion components with a layout prop will attempt to detect and animate layout changes every time they commit a React render.

It might be the case that components in different trees affect each other's layout.

Reorder

Create drag-to-reorder effects with a simple set of components.

The Reorder components can be used to create drag-to-reorder lists, like reorderable tabs or todo items.

AnimatePresence

Animate components when they're removed from the React tree.

AnimatePresence allows components to animate out when they're removed from the React tree.

To put it simply, AnimatePresence is a special component that detects whenever a component is removed from the DOM. So, we can add animation to our components right before it disappear!

Implementation #1

In implementation part 1, we will write a todo app with a textarea for creating task and list of ul/li for displaying tasks.

textarea component will be slided down from top to bottom a bit with bouncing effect and items of the list will be slided in from left to right one after one on load and each item will be faded out from left to right on delete.

Firstly, we will use LayoutGroup to group our animated components and use AnimatePresence to wrap around the li elements so we can add exit animation onDelete:

App.js
        <LayoutGroup>
          <div className="max-w-3xl mx-auto my-6">
            <motion.form
              initial={{
                y: "-100",
                opacity: 0,
              }}
              animate={{
                y: 0,
                opacity: 1,
                transition: {
                  duration: 1,
                  delay: 0.2,
                  type: "spring",
                  bounce: 0.85,
                },
              }}
              whileTap={{
                scale: 0.95,
              }}
              whileHover={{
                y: -5,
                transition: {
                  type: "spring",
                  bounce: 0.85,
                },
              }}
              className="w-full"
            >
              <textarea
                className="
                    form-control
                    block
                    w-full
                    px-2
                    py-1.5
                    text-base
                    font-normal
                    text-gray-700
                    bg-white bg-clip-padding
                    border border-solid border-gray-300
                    rounded
                    m-0
                    focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none"
                id="exampleFormControlTextarea1"
                rows="4"
                placeholder="Your new task"
                value={newTask.description}
                onChange={handleChange}
                onKeyDown={handleEnterPress}
              ></textarea>
            </motion.form>
          </div>
          <motion.ul
            layout
            className="overflow-y-auto"
            style={{ maxHeight: "80vh" }}
          >
            <AnimatePresence>
              {tasks.map((task, i) => {
                return (
                  <motion.li
                    layout
                    initial={{ x: -100, opacity: 0 }}
                    animate={{
                      x: 0,
                      opacity: 0.8,
                      transition: {
                        type: "spring",
                        stiffness: 150,
                        duration: 1,
                        delay: i / 4,
                      },
                    }}
                    exit={{
                      x: 50,
                      opacity: 0,
                      transition: {
                        ease: "easeInOut",
                        duration: 0.25,
                        delay: 0.15,
                      },
                    }}
                    whileHover={{
                      opacity: 1,
                      scale: 1.03,
                      transition: {
                        type: "spring",
                        bounce: 0.85,
                      },
                    }}
                    key={task.id}
                    className="max-w-3xl p-4 mx-auto my-4 rounded-lg shadow-lg bg-slate-200"
                  >
                    <h4 className="text-lg font-semibold text-gray-800">
                      {task.description}
                    </h4>
                    <p className="mt-2 text-sm font-semibold text-gray-600">
                      Created on {task.date}
                    </p>
                    <div className="flex justify-end space-x-2">
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        className="w-6 h-6"
                        fill="none"
                        viewBox="0 0 24 24"
                        stroke="currentColor"
                      >
                        <path
                          strokeLinecap="round"
                          strokeLinejoin="round"
                          strokeWidth={2}
                          d="M5 13l4 4L19 7"
                        />
                      </svg>
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        className="w-6 h-6"
                        fill="none"
                        viewBox="0 0 24 24"
                        stroke="currentColor"
                      >
                        <path
                          strokeLinecap="round"
                          strokeLinejoin="round"
                          strokeWidth={2}
                          d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
                        />
                      </svg>
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        className="w-6 h-6 cursor-pointer"
                        fill="none"
                        viewBox="0 0 24 24"
                        stroke="currentColor"
                        onClick={() => removeTask(task.id)}
                      >
                        <path
                          strokeLinecap="round"
                          strokeLinejoin="round"
                          strokeWidth={2}
                          d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
                        />
                      </svg>
                    </div>
                  </motion.li>
                );
              })}
            </AnimatePresence>
          </motion.ul>
        </LayoutGroup>

Implementation #2

In this part, we will use Reorder component in place of ul/li - elements, so, we can reorder our task list:

App.js
<Reorder.Group
    ...
    axis="y" values={tasks} onReorder={setTasks}>
  <AnimatePresence>
    {tasks.map((task, i) => {
      return (
        <Reorder.Item
          ...
          key={task.id}
          value={task}
          className="max-w-3xl p-4 mx-auto my-4 rounded-lg shadow-lg bg-slate-200">
          ...
        </Reorder.Item>
        );
      })}
  </AnimatePresence>
</Reorder.Group>

Ref => Framer Motion tutorial: How to easily create React animations

Ref => Framer Motion - beautiful animations and interactions for React

Source Code.