frontmatter

A scroll animation with React hooks

October 11, 2021

On the marketing page of my web development agency I use a video loop of a running greyhound dog as a hero image. In this article I described how I used React hooks to animate this loop, so the dog runs across the screen when the user scrolls down. Check it out here.

The objectives

  • When the user scolls down the element should move to the right.
  • It should start moving slowly and accelerate.
  • Its position should be reset when the user scrolls up.
  • This one-off animation on the landing page should use as little code as possible, so instead of using a scroll hook from a library we’ll roll our own.

The event listener and useEffect

Let’s start off with the basics: adding an event listener for the scroll event and logging the scroll value to the console. We use a useEffect hook to add the event listener after the DOM has loaded. We want the effect to run only once when the component is mounted, so weadd an empty dependency array. The effect returns a function that removes the event listener to prevent a memory leak.

The window object and server-side rendering

To prevent a ‘window is not defined’ error while server-side rendering, the getScrollPosition function returns zero if window is undefined.

function getScrollPosition() {
if (typeof window === "undefined") return 0;
return window.scrollY;
}
useEffect(() => {
function logScroll() {
console.log(getScrollPosition());
}
window.addEventListener('scroll', logScroll);
return () => {
window.removeEventListener('scroll', logScroll);
};
}, []); // empty array means the effect will run only once

Using transform: translateX to move the element

Now let’s use the scroll position to move the element. First we need a ref to the element, so that we can manipulate it’s position. Then we change its position using the style.transform property.

const element = useRef(null);
useEffect(() => {
function moveElement() {
element.current.style.transform = `translateX(${getScrollPosition()}px)`
}
window.addEventListener('scroll', moveElement);
return () => {
window.removeEventListener('scroll', moveElement);
};
}, [])
return (
<div ref={element} />
)

requestAnimationFrame to the rescue

Next we’ll use the requestAnimationFrame function to tell the browser that we’re going to start shifting the furniture around. The browser will make sure that the animation functions gets called before the next repaint.

function handleScroll() {
requestAnimationFrame(moveElement)
}
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};

A home grown ease-in function

The next step is to change the movement of the element so that it starts to move slowly and accelerate as the user is scrolling down. To achieve this we’ll use an exponential function. The divisor controls the initial slow down and the exponent changes the acceleration.

function easeIn({ value, divisor, exponent }) {
return Math.pow(value / divisor, exponent)
}

Capturing the scroll direction

The last step is to reset the position of the element when the user scrolls up. To capture the scroll direction we add a ref that holds the value of the scroll position at the last scroll event. By comparing this value to the current scroll position we determine whether the user is scrolling up. A second ref holds the value of the current scroll direction.

const lastPosition = useRef(getScrollPosition());
const isScrollingUp = useRef(false);
function setScrollDirection() {
const scrollPosition = getScrollPosition();
if (scrollPosition < lastPosition.current) {
isScrollingUp.current = true;
} else {
isScrollingUp.current = false;
}
lastPosition.current = scrollPosition;
}
Now we can use the scroll direction to reset the position of the element when the user scrolls up.
const offset = isScrollingUp.current
? 0
: easeIn({
value: getScrollPosition(),
divisor: 50,
exponent: 4,
});

Putting it all together

To see how all the pieces work together here’s an interactive stack blitz preview and editor that shows how all the code is working together. Click on the editor tab to view and tweak the code.



Profile picture

Written by Heiner Behrends.

heinerbehrends.eu is home to my portfolio and blog. Here I present personal projects focused on interactivity and blog posts that document my learning progress. Feel free to check out my portfolio. You can follow me on Twitter.

© 2022, Built with love by flyfi
Check out my portfolio on the home page.