DEV Community

Cover image for Creating a parallax effect using react-spring
Robbert van Caem
Robbert van Caem

Posted on • Edited on

Creating a parallax effect using react-spring

I can hear you think. "Another blog post using react-spring?! This guy..." Well, turns out I'm having quite a good time using react-spring for all kinds of animations/movements. Also, writing this down helps me better understand how it works. So tough luck, here goes nothing.

There are a couple of good libraries out there that can help you achieve a parallax effect quite easily. But the basics for a parallax effect are pretty simple: component X (or a part of it) moves with a different speed horizontally or vertically than component Y, which creates a sense of depth. So achieving the same without a plugin specifically for this effect is actually not that hard.

The objectives

  1. Attaching a scroll listener
  2. Applying the parallax effect by setting the translateY property

1. Attaching a scroll listener

Attaching a scroll listener is actually really easy uing React's useEffect hook. We pass the hook a function that adds an event listener. This function has scroll as it's first argument, and a function handleScroll as second argument. We return a function which removes this event listener. By returning this function we're telling React to do some cleanup when the component is updated or unmounted.

import React, { useEffect, useRef } from 'react';

const Comp = () => {
  const ref = useRef();

  const handleScroll = () => {
    const posY = ref.current.getBoundingClientRect().top;
    const offset = window.pageYOffset - posY;
    console.log(offset);
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  });

  return (<div ref={ref}>Contents of your component</div>)
}

export default Comp;
Enter fullscreen mode Exit fullscreen mode

Notice that in the handleScroll method we're calculating the relative Y distance of our component by subtracting the top property of the bounding client rect from the current offset of the window. If you don't do this, the impact of your parallax effect will be based on where (vertically) your component is placed. By using this nifty correction, we'll make sure that our offset has a negative value as long as our component's top is below the viewport's top. When our component's top has passed the viewport's top, the value for offset becomes positive.

Notice, no react-spring has been used yet ;-)

2. Applying the parallax effect

Now that we have the relative Y position of our component, we can start using this to create the parallax effect. We'll use a basic spring for this and define the default offset (which is 0) using the useSpring method. This returns both the interpolated value and an update/set function. We'll use this update/set function in our handleScroll method.

*I've explained a bit more about the useSpring method in one of my previous posts, see this link if you want to know more about it.

import React, { useEffect, useRef } from 'react';
import { useSpring } from 'react-spring';

const Comp = () => {
  const ref = useRef();
  const [{ offset }, set] = useSpring(() => ({ offset: 0 }));

  const handleScroll = () => {
    const posY = ref.current.getBoundingClientRect().top;
    const offset = window.pageYOffset - posY;
    set({ offset });
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  });

  return (<div ref={ref}>Contents of your component</div>)
}

export default Comp;
Enter fullscreen mode Exit fullscreen mode

Right now, we have everything we need to enable our parallax effect. So the next step would be to start moving stuff around. For this example, we'll be using some 'dirty' inline styling, you could use something like styled-components or any other tool for this.

import React, { useEffect, useRef } from 'react';
import { animated, useSpring } from 'react-spring';

// You can use this `calc` method to increase the impact
// of the effect by playing around with the values and units.
const calc = o => `translateY(${o * 0.1}px)`;

const Comp = () => {
  const ref = useRef();
  const [{ offset }, set] = useSpring(() => ({ offset: 0 }));

  const handleScroll = () => {
    const posY = ref.current.getBoundingClientRect().top;
    const offset = window.pageYOffset - posY;
    set({ offset });
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  });

  return (
    <div style={{
      background: '#123456',
      position: 'relative',
      width: '100vw',
      height: '400px',
      ref={ref}
    }}>
      <animated.div style={{
        background: '#654321',
        position: 'absolute',
        width: '100vw',
        height: '100px',
        transform: offset.interpolate(calc)
      }} />
    </div>
  )
}

export default Comp;
Enter fullscreen mode Exit fullscreen mode

And that's it! As you can see all it takes is to define an animated.div with a style object. By interpolating the offset provided through react-spring with a function calc we have full control over the impact of the effect. You could for example change the calc function to manipulate the translateX property. This would make our parallax effect to act horizontally.

Check out the ugly but working CodeSandbox below

Got questions or feedback?

Did you find this useful? Or do you know of a different cool way to achieve a parallax effect? I'm thinking of trying to find a nice way of defining different depths 🤔 If you have any different topics you'd like to hear about, let me know! Next topics I'll cover will probably be:

  • Setting up and writing your first tests with Jest
  • How to setup staging/production environments using Now

Don't forget to start following me here, on Medium or on Twitter!

Top comments (1)

Collapse
 
lucaszapico profile image
LucasZapico

Thank mate,

This is one of the cleaner solutions I have seen around. Also, I don't mind the post as I understand the multiple effects of posting, reinforce learning, show movement for employers and clients, etc... so keep it up! 🤩 😎 Cheers!