framer-motion

Floating parallax images with React and framer-motion

Jamie Lottering
Workbench
Published in
4 min readAug 15, 2019

--

In this article I’ll go over how to achieve the following technique using React and framer-motion to give your content a floaty/parallaxy feel that can help add a little flair to your layouts.

The finished product.

The first thing we’ll want to do our is create our ParallaxItem component and import the correct libraries.

Doesn’t do much yet. We’ll want our component to take children and className props for customization.

The next thing we’ll want to do is use framer-motion’s useViewportScroll so that we can hook into the scroll event and grab the scrollY value. We will use this to compute relative parallax values for each item.

Now that we have access to the window’s scrollY value, lets use another useful hook that framer-motion provides: useTransform.

useTransform takes a value and maps it onto another value. Let’s add that and then use the motion.div component to plug in our spring.

The useTransform here is saying:

“When scrollY falls between 0–500 return a value within the range of 0%-100%. So if scrollY were 250 the output value would be 50%.”

Great, but a scroll range of 0–500 will only work when you’ve scrolled between 0 to 500 pixels. We want to make this value relative to our individual items so we can place them anywhere on the page.

To do this, we need to get the offsetTop of our element, and then plug that value into our transform function. Let’s create an offsetTop state to do this.

So this still doesn’t work yet, we need to add a buffer zone to our range so that the effect is visible. Let’s set an arbitrary value of 500:

Now our code in the transform here is saying when scrollY is equal to the offsetTop minus 500 pixels, return 0%, and when it’s equal to offsetTop plus 500 pixels, return 100%. Now we should start seeing the effect, but it’s quite strong because our output range is 0 to 100%. Lets adjust that a bit.

Hopefully its working and looking better now. But you might notice that it clips content when the parallax effect is at the maximum value. That’s not really ideal. We can combat this by setting a min-height to our item that accounts for the initial height + any space that it needs to move around. Let’s add a function to calculate that and call it in our previous useLayoutEffect

In this code we also created a new variable range and converted it to a decimal instead of a string of 20%.

Now the content should stop clipping and your parallax items have a nice amount of breathing room with which to move around.

Now let’s adjust the spring values to make them feel smoother, and give add a little element of randomness.

By randomizing the mass, elements should move a little faster or a little slower instead of all moving together in lockstep, which helps it to feel more organic.

The final step is making sure it’s responsive. Since we only calculate the offsetTop and minHeight whenever the ref changes, resizing the window could cause overlapping content or tall blocks of empty space. Let’s add a window resize listener in our useLayoutEffect to fix that:

Take a look at index.js in the code sandbox above to see the final code which is organized a little better and get a feel for how it works in practice.

Caveats

If the content you’re using inside of your ParallaxItem component does not have a defined height by the time it runs setMinHeight a race condition will most likely cause the min-height to remain as the default value of ‘auto’, meaning your content will clip. This can happen when using images, lazy loaded or otherwise. A good solution for this is to use aspect ratio sizing using padding-bottom. Here’s a good writeup: https://css-tricks.com/aspect-ratio-boxes/. You can see an example in action in the code sandbox at the top of this article.

--

--