Eliaslog.pw

The motion component of framer motion has a whileHover prop which allows you to declare a state that the component is animated to, while the cursor is hovering over it.

This is great for signaling interactivity to your user. Buttons can scale up, box shadows can grow and links can change color.

If, however, your animation is composed of several states and should keep playing after the hover event, you’ll need to go a litte further.

useAnimation()

To start the animation, we need to get a handle of the animation-instance itself. Have a look at the following code:

//import {motion, useAnimation} from 'framer-motion'

const divAnimationControls = useAnimation()

;<motion.div animate={divAnimationControls}></motion.div>

Our soon-to-be-animated div has only one animate prop where we pass in the divAnimationControls. This gives us access to the .start(), .stop() and .sequence() methods.

We can now call divAnimationControls.start() from anywhere in our component to start the animation.

Animation Variants

Well, we can now tell the div to start the animation, but it doesn’t know what to animate.

This is where animation variants come into play. In our component, we add a plain old JS-object like that:

const divAnimationVariants = {
  init: {
    y: 0,
  },
  anim: {
    y: -20,
  },
}

With that sorted, we can now tell our <motion.div> to start the animation when the user starts hovering:

<motion.div
  animate={divAnimationControls}
  onHoverStart={() => {
    logoAnimationControls.start(divAnimationVariants.anim)
  }}
></motion.div>

The onHoverStart-prop takes in a function. Within that function we take our logoAnimationControls and call the .start() method with the argument being the one it should animate to (divAnimationVariants.anim).

And it works! If you now hover over your element, it moves up and stays there.

If you want your element to return to the initial position, update your divAnimationVariants like that:

const divAnimationVariants = {
    init: {
      y: 0
    },
    anim: {
      y: -20
			transition: {
        type: "tween",
        repeat: 1,
        repeatType: "reverse",
      },
    }
}

*But, there is one problem*.

The problem

If your user is of the impatient type or your animation very long, chances are, that they hover over the element again before the first cycle is complete. This will have some quirky, unexpected results depending on the animation.

giphy1.gif

To combat this, we have to rely on reacts good ol’ useState.

//import {useState} from 'react'

const [isAnimationPlaying, setIsAnimationPlaying] = useState(false)

Initially the animation doesn’t play, so we set the value to false.

Then we only have to update our onHoverStart function and add an onAnimationComplete prop like so:

<motion.div
  animate={divAnimationControls}
  onHoverStart={() => {
    if (!isAnimationPlaying) {
      setIsAnimationPlaying(true)
      logoAnimationControls.start(divAnimationVariants.anim)
    }
  }}
  onAnimationComplete={() => {
    setIsAnimationPlaying(false)
  }}
></motion.div>

The onAnimationComplete prop also takes in a function, where we are just setting the isAnimationPlaying state to false, allowing it to be played again with the next hover.

And that’s it, folks. If you were lost on the way down here, or just want the complete answers, check below:

Full code

import React, {useState} from 'react'
import {motion, useAnimation} from 'framer-motion'

const AnimatedComponent = () => {

	const [isAnimationPlaying, setIsAnimationPlaying] = useState(false);

	const divAnimationControls = useAnimation();

	const divAnimationVariants = {
	    init: {
	      y: 0
	    },
	    anim: {
	      y: -20
				transition: {
	        type: "tween",
	        repeat: 1,
	        repeatType: "reverse",
	      },
	    }
	}

	return(
		<motion.div

			animate={divAnimationControls}

			onHoverStart={() => {
					if (!isAnimationPlaying) {
			      setIsAnimationPlaying(true)
			      logoAnimationControls.start(divAnimationVariants.anim)
			    }
				}}

				onAnimationComplete={() => {
		      setIsAnimationPlaying(false)
		    }}

		>I go up and down!</motion.div>
	)
}
The creation of this post was made possible by coffee.
Buy me a coffee