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
- Attaching a scroll listener
- 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;
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;
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;
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)
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!