EngNotebook Logo

Engineering Notebook

Back to home

Motion Animation Library

Engineering Note #04

Last updated · September 6, 2026

AnimationReact

Welcome to Motion Animation 101.

Most modern orchestration libraries (like Framer Motion) operate on a declarative mental model. You don't tell the element how to move step-by-step; you describe its states and tell it when to switch.

Here is the breakdown of how animations are actually triggered, structured, and scaled.

1. The Core Trifecta: initial, animate, and exit

Every basic animation relies on these three lifecycle props. They target the structural state of the component.

  • initial: The starting line. It defines the visual state of the element before it renders on the screen. If you set this to false, it skips the mounting animation entirely.
  • animate: The destination. The library constantly watches this prop. If the value here changes, the library automatically calculates the transition from the old value to the new one.
  • exit: The parting words. Defines how the element should animate when it's removed from the UI. (Note: This usually requires a wrapping component like <AnimatePresence> to work, because standard React unmounts elements instantly before they can animate).

JavaScript

// A simple fade-in-up example
<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: -20 }}
/>

2. Trigger Mechanism A: The Direct animate Prop

The most straightforward way to trigger an animation is by passing a dynamic value directly into the animate prop using component state.

How it works:

You tie your inline styles to a React state variable. When the state changes, a re-render triggers, and the library smooths out the difference.

JavaScript

const [isActive, setIsActive] = useState(false);

return (
  <motion.button
    animate={{
      scale: isActive ? 1.2 : 1,
      backgroundColor: isActive ? "#ff0055" : "#0055ff"
    }}
    onClick={() => setIsActive(!isActive)}
  >
    Click Me
  </motion.button>
);
  • Best for: Isolated, simple components that only have two or three visual states (e.g., toggles, accordions, single buttons).
  • The Downside: It gets incredibly messy when components scale or when multiple elements need to coordinate.

3. Trigger Mechanism B: Variants (The Pro Way)

Variants are the secret sauce of clean animation code. Instead of passing messy object literals directly to the props, you extract your visual states into a separate configuration object.

The Anatomy of Variants

You define named "states" (like hidden and visible), and then pass those names as strings to initial and animate.

JavaScript

// 1. Define the master blueprint
const boxVariants = {
  hidden: { opacity: 0, x: -100 },
  visible: { opacity: 1, x: 0, transition: { duration: 0.5 } }
};

// 2. Feed it to the component
<motion.div
  variants={boxVariants}
  initial="hidden"
  animate="visible"
/>

Why Variants are Actually Used:

  1. Propagation (The Domino Effect): If a parent component uses variants, any child motion component with the same variant names will automatically trigger its own animation without needing explicit state passed down.
  2. Orchestration: Variants allow parents to control the timing of their children using props like delayChildren or staggerChildren.

JavaScript

const listVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: { staggerChildren: 0.1 } // Automatically staggers child entry!
  }
};

const itemVariants = {
  hidden: { y: 20, opacity: 0 },
  visible: { y: 0, opacity: 1 }
};

return (
  <motion.ul variants={listVariants} initial="hidden" animate="visible">
    <motion.li variants={itemVariants} />
    <motion.li variants={itemVariants} />
    <motion.li variants={itemVariants} />
  </motion.ul>
);

4. Trigger Mechanism C: Gesture Shortcuts

Sometimes you don't want to manage state hooks for basic user interactions. Good animation libraries offer built-in listener props that trigger animations automatically based on user intent.

PropTrigger ConditionExample
whileHoverUser moves mouse over the element.whileHover={{ scale: 1.05 }}
whileTapUser clicks or presses down on the element.whileTap={{ scale: 0.95 }}
whileFocusElement gains keyboard focus (great for accessibility).whileFocus={{ target: "2px solid blue" }}
whileInViewElement enters the browser viewport (Scroll-trigger).whileInView={{ opacity: 1 }}

💡 Pro-Tip: Gesture shortcuts play perfectly with variants. You can pass a variant string to a gesture: whileHover="brighten".

5. Trigger Mechanism D: Programmatic Controls (useAnimation)

There are times when declarative props fall short—like when you need to trigger an animation after an API call finishes, or sequence a complex chain of non-linear events.

For this, you use imperative controls. This gives you a remote control (controls) that can fire .start() and .stop() functions inside standard JavaScript logic.

JavaScript

const controls = useAnimation();

const handleSaveData = async () => {
  await api.submitPayload();
  // Trigger animation imperatively once the promise resolves
  controls.start({
    x: [0, -10, 10, -10, 10, 0], // Shake keyframes
    transition: { duration: 0.5 }
  });
};

return <motion.form animate={controls} onSubmit={handleSaveData} />;

Summary Checklist: Which one should you use?

  • Use Direct Props if it's a tiny component with one basic state toggle.
  • Use Variants for 80% of your work—especially lists, grids, layouts, and anything requiring orchestrated timing.
  • Use Gestures for purely tactile feedback (hovers, clicks, scroll-reveals).
  • Use Programmatic Controls for async logic, complex sequencing, or form validations.

More Notes

Architecture Diagrams Are Answering the Wrong Question

Engineering Note #01

ArchitectureMedia SystemsDocker

SRT Field Guide

Engineering Note #02

Media SystemsDocker

Systemctl Field Guide

Engineering Note #03

DockerLinux
See all notes

Built by Jonah Sol