Framer Motion


Uncategorized

Updated Aug 27th, 2022

NPM link here

Colby Fayock has a good video here

Fireship of course…here (Leveraged when building out a modal/backdrop). Notes below.

With Styled Components

An extra step but still simple. Here. And here.

With Emotion

Didn’t see any issue with a simple fade in

With TypeScript

Package seemingly comes with types built in.

Using the API

Fade-In effect:

import { motion } from "framer-motion"

// in the JSX
<motion.div
  initial="hidden"
  animate="visible"
  variants={{
    hidden: {
      scale: 0.8,
      opacity: 0
    },
    visible: {
      scale: 1,
      opacity: 1,
      transition: {
        delay: 0.4
      }
    }
  }}
>
  <CarImage />
</motion.div>

HOVER EFFECT

Note: converting and li to motion.li the applied className no longer works.

So does it work on a custom styled component? With a little more work but yes.

Notes from Fireship Video

Not the only way to handle animation in react but (framer motion) is the most intuitive.

swap out a button for “motion.button” as “motion” is an HTML element with special animation props.

<motion.button>
  whileHover={{scale: 1.1}}
  whileTap={{scale: 0.9}}
</motion.button>

But why would use framer motion over regular CSS animations? 1.) Easier and results in more simplified code. 2.) Framer can do a lot of things that CSS can’t like listen to different browser events and integrate with the actual state of your react application. More developer friendly to work with.\

Create a modal

Two moving parts, a backdrop and a modal window where user’s attention is focused.


const Backdrop = ({children, onClick}) => {
  return (
    <motion.div
      className="backdrop"
      onClick={onClick}
      initial={{opacity: 0}}
      animate={{opacity: 1}}
      exit={{opacity: 0}}
    >
      {children}
    </motion.div> 
  )
}

export default Backdrop

Note: This is a dumb component that doesn’t have it’s own internal state to manage.

The CSS

.backdrop {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  background: #000000e1;
  display: flex;
  align-items: center;
  justify-content: center;
}

Note: Adding additional two characters to the hex color for the opacity

To add the modal component:

const Modal = ({handleClose, text}) => {

  return (
    <Backdrop onClick={handleClose}>
      <motion.div
        onClick={(e)=> e.stopPropogation()}
        className="modal orange-gradient"
        variants={dropIn}
        initial="hidden"
        animate="visible"
        exit="exit"
    >
      {children}
    </motion.div>
    </Backdrop>
  )
}

export default Modal

Note: event.stopPropogation deals with event bubbling and will prevent clicks on the modal from bubbling to the backdrop. normally when you click on something it bubble up the DOM to find the first event handler to take care of it. This default browser behavior would cause the modal to automatically close anytime the content inside of it is clicked.

Also note: The “dropIn” object allows you to define multiple animation states. you can leverage to create a complex sequence of animations.

const dropIn = {
  hidden: {
    y: "-100vh",
    opacity: 0
  },
  visible: {
    y: 0,
    opacity: 1,
    transition: {
      duration: 0.1s,
      type: "spring",
      damping: 25,
      stiffness: 500
    }
  },
  exit: {
    y: "100vh",
    opacity: 0
  }
}

The modal CSS

.modal {
  width: clamp(50%, 700px, 90%);
  height: min(50%, 300px);
  margin: auto;
  padding: 0 2rem;
  border-radius: 12px;
  flex-direction: column;
  align-items: center;
}

Note: You can use clamp and min functions from CSS to reduce the use of media queries.

But how do we actually trigger the backdrop and modal? Go back to the root app component and leverage the “useState” hook along with the modal.

const [modalOpen, setModalOpen] = useState(false)

const close = () => setModalOpen(false)
const open = () => setModalOpen(true)

Then go to the JSX and add an “onClick” event handler to the “motion.button”

onClick={()=> (modalOpen ? close() : open() )}

Note: I did not know you could use a ternary to conditionally set the value to an “onClick” event handler?!

Also be sure to conditionally render the modal itself

{modalOpen && <Modal modalOpen={modalOpen} handleClose={close}/>}

At this point the animate-in works properly but not the animate-out is non-existent. The reason this happens is because the modal is completely removed form the DOM before the animation finishes, (I’ve definitely encountered this before). Framer motion has a mechanism called “AnimatePresence” to rescue us from this predicament.

import {motion, AnimatePresence} from "framer-motion"
<AnimatePresence>

  // Disable any initial animations on children that
  // are present when the component is first rendered
  initial={false}

  // Only render one component at a time
  // The exiting component will finish its exit
  // animation before entering component is rendered
  exitBeforeEnter={true}

  // Fires when all exiting nodes have completed animating out
  onExitComplete={() => null}  

  {modalOpen && <Modal modalOpen={modalOpen} handleClose={close}/>}

</AnimatePresence>

Bonus tip: Add the “drag” prop to the motion div and it’s now draggable?!

Digging on the Toast Notification Stack, flash messaging as seen in the live demo here. This is part of fireship pro. Crazy that pasting an emoji into the text input for this feature showed the emoji?! And when clicked rendered this along with the text as part of the toast notification.