Animate when scrolled into view

TL;DR

The motion component e.g. <motion.div /> supports a prop that allows you to animate when visible on the screen. Leveraging the prop: whileInView, we can play one-off animations when the user scrolls down and we can use transition to play a looped animation.

Here's an interactive example in CodeSandbox:

How can I use whileInView?

In the example, there's a few different ways I've used the whileInView prop to support some use cases that are a bit more challenging in CSS but simple with Framer Motion, let's take a look at them.

Animate once

This use case is the fastest to start with and can be seen in the Banner component:

Expand to see the full Banner component code

import { motion } from "framer-motion";
import Wave from "./Wave";

export default function Banner({ onDismiss }: { onDismiss: () => void }) {
return (
<motion.aside
className="overlay"
whileInView={{ backgroundColor: "#2b0687" }}
initial={{ backgroundColor: "#dadada" }}
exit={{
opacity: 0,
backgroundColor: "#dadada",
transition: { backgroundColor: { delay: 0 }, opacity: { delay: 0.1 } }
}}
transition={{
duration: 0.25,
delay: 0.5
}}
>

<Wave /> There's something new avialable, right now!

<motion.button
className="close-btn"
onClick={onDismiss}
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.5 }}
>
Close
</motion.button>
</motion.aside>
);
}

The most important prop is whileInView, much like animate, we supply an object with the properties we want to animate, in our case - backgroundColor. This will cause the animation to play when we scroll into view, I've made this animation a bit more obvious by adding a delay so you can see it after it enters the screen.

Warning on animating colours

When animating colours in Framer Motion, you might run into issues if you don't use hex, rgb, hsl, etc. For example,
trying to animate between named colours -
navy
and
orange
won't work.
If you're wondering why you can't animate between colours in Framer Motion, change them to one of these and try again!

<motion.aside
whileInView={{ backgroundColor: "#2b0687" }}
initial={{ backgroundColor: "#dadada" }}
transition={{
duration: 0.25,
delay: 0.5
}}
>

When would I animate once?

The best case I see is to give attention to something. I used a banner in the example because it's a common sight on a lot of websites. I also restricted the animation I chose, I started off with animating the position (x and/or y coordinates) but moving across the page when scrolling is very distracting. There are ways this could work but not on an element this size.

Animate continuously

Using the same principals as before, the Wave component animates continuosly but only when it's within the viewport using the whileInView prop, this time I wanted to creaet a little rotation back and forward so I'm also using keyframes.

Keyframe animations in Framer Motion are as easy as changing a single value, to an array of values. The following property
scale: [1.0, 1.3, 1.0]
tells Framer Motion to animate the scale between three values.

To achieve a continuous wave, I use the transition prop but there are two things to note:

I want to emphasise the wave the first time so the animation should only play once when it is on screen. To do this, I added scale: { duration: 1, delay: 3 } to transition prop.

To play the rotation animation continuously with pauses in-between, I added rotate: { duration: 1.2, repeat: Infinity, repeatDelay: 3 } to the transition prop.

import { motion } from "framer-motion";

export default function Wave() {
return (
<motion.div
className="wave"
whileInView={{ scale: [1.0, 1.3, 1.0] }}
animate={{ rotate: [12, 0, -12, 0, 12, 0, -12, 0, 12, 0, -12, 0] }}
transition={{
rotate: { duration: 1.2, repeat: Infinity, repeatDelay: 3 },
scale: { duration: 1, delay: 3 }
}}
initial={{ rotate: 0 }}
>

👋

</motion.div>
);
}

When would I animate continuously?

I think the best use cases are for emphasis or if you're adding more creative animations to the experience. I chose to go with a simple wave animation to go with the emoji with the idea that there's an important message in the banner that we want to highlight.

Animate after a delay

Lastly, this is very simliar to our previous two components but is something that can be quite useful, it's the BouncyArrow component.

import { motion } from "framer-motion";

export default function BouncyArrow() {
return (
<motion.div
whileInView={{ y: [-8, 0] }}
transition={{ repeat: Infinity, duration: 0.3, delay: 1 }}
>

🔽

</motion.div>
);
}

This simple technique of adding a delay in the transition prop and setting an animation to happen when the element enters the viewport allows for an intersting use case.

When would I animate after a delay?

I've chosen to add BouncyArrow as a way of hinting to the user to scroll down. This isn't subtle at all but it could be used to encourage users to progress or offer hints when they get stuck. Choosing a larger delay is preferable but there is lots you could do with this technique when it comes to user onboarding.

Wrap up

This is just a brief look at the whileInView prop and how we can use it with other techniques in Framer Motion. If you want to learn about creating page transitions or exploring what Framer Motion can do, I have more framer motion articles avilable.

Edit the sandbox and experiment

Read more about how to use whileInView in the Framer Motion docs