Animate when scrolled into view
Triggering animations on scroll with Framer Motion
3 minute read
( )This post is also available in different formats so you can read on the go or share it around!
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
}}
>
<p className="description">
<Wave /> There's something new avialable, right now!
</p>
<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 }
totransition
prop. - To play the rotation animation continuously with pauses in-between, I added
rotate: { duration: 1.2, repeat: Infinity, repeatDelay: 3 }
to thetransition
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 }}
>
<span role="img" aria-label="wave">
👋
</span>
</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 }}
>
<span role="img" aria-label="wave">
🔽
</span>
</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.
A Fullstack Software Engineer working with React and Django. My main focus is JavaScript specialising in frontend UI with React. I like to explore different frameworks and technologies in my spare time. Learning languages (programming and real life) is a blast.