DEV Community

Diego (Relatable Code)
Diego (Relatable Code)

Posted on • Originally published at relatablecode.com

How to easily trigger react-spring animation when in view

react-spring is a library to easily create and integrate animations in a React app. A possible use-case of the library is triggering an animation only when a particular ref or component is in view. In this tutorial, I’ll cover how to set up react-spring and the hooks necessary!

This guide assumes familiarity with React and react-spring and the fundamentals of React Hooks.

Requirements

For the sake of the example let’s say we have a webpage that is split into two sections. Both sections take up the entire viewport. When scrolling down to the second page let’s have a header fly in from the right.

Getting started

First, let’s set up the page with just the visual portion:

<div className="App">
      <div style={{ backgroundColor: "grey" }}>
        <h1>This is my first page</h1>
      </div>
      <div style={{ backgroundColor: "white" }}>
        <h2>This should come flying in</h2>
      </div>
</div>
Enter fullscreen mode Exit fullscreen mode

I’ve added some styles to better differentiate between both sections.

Now with the styles out of the way let’s set up the react-spring hook to make it fly in. We need to use useSpring to set up the animation and change the h2 element to an animated h2 element.

 const headerStyle = useSpring({
    config: { duration: 500 },
    from: { opacity: 0, left: "-500px" },
    to: {
      opacity: 1,
      left: "-500px"
    }
  });

  return (
    <div className="App">
      <div style={{ backgroundColor: "grey" }}>
        <h1>This is my first page</h1>
      </div>
      <div style={{ backgroundColor: "white" }}>
        <animated.h2 style={headerStyle}>
          This should come flying in
        </animated.h2>
      </div>
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

Perfect. But wait our hook is missing some validations in order to transition between two styles. Luckily, modern browsers offer access to the Intersection Observer API. Without getting into the nitty-gritty, this API lets us detect elements that are visible.

Intersection Observer

Let’s go ahead and create the hook we’re going to use:

function useIntersectionObserver(
  elementRef,
  { threshold = 0, root = null, rootMargin = "0%", freezeOnceVisible = false }
) {
  const [entry, setEntry] = useState();

  const frozen = entry?.isIntersecting && freezeOnceVisible;

  const updateEntry = ([entry]) => {
    setEntry(entry);
  };

  useEffect(() => {
    const node = elementRef?.current;
    const hasIOSupport = !!window.IntersectionObserver;

    if (!hasIOSupport || frozen || !node) return;

    const observerParams = { threshold, root, rootMargin };
    const observer = new IntersectionObserver(updateEntry, observerParams);

    observer.observe(node);

    return () => observer.disconnect();
  }, [elementRef, threshold, root, rootMargin, frozen]);

  return entry;
}
Enter fullscreen mode Exit fullscreen mode

Alright, let’s example how this hook works. Essentially it receives a ref, the node stored in the ref is then set to be observed by the IntersectionObserver. A callback is passed to the observer so it is called when in view.

A local state is then set and we return this same entry. An additional parameter is available to freeze the trigger so it isn’t executed multiple times.

Hooking it up

Next, let’s create the ref and hook it up to the new hook we just created:

  const triggerRef = useRef();
  const dataRef = useIntersectionObserver(triggerRef, {
    freezeOnceVisible: true
  });

  return (
    <div className="App">
      <div style={{ backgroundColor: "grey" }}>
        <h1>This is my first page</h1>
      </div>
      <div style={{ backgroundColor: "white" }}>
        <animated.h2 style={headerStyle}>
          This should come flying in
        </animated.h2>
        <div ref={triggerRef} />
      </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

We set up a trigger ref right below the header and we’re sending this same ref to useIntersectionObserver.

If we go ahead and console.log() the dataRef we should see it getting populated if we scroll down.

Inside the object there is a particular property that interests us:

Values of entry of IntersectionObserver

isIntersecting is exactly what it says. When it’s visible and intersecting with the DOM/viewport it will change over to true. Let’s go ahead and set up the useSpring to validate this property.

const headerStyle = useSpring({
    config: { duration: 500 },
    from: { opacity: 0, left: "-500px" },
    to: {
      opacity: dataRef?.isIntersecting ? 1 : 0,
      left: dataRef?.isIntersecting ? "0px" : "-500px"
    }
  });
Enter fullscreen mode Exit fullscreen mode

And that’s it! Here’s the working example:

If you liked the tutorial or have anything else you’d like to see let me know in the comments below.

Top comments (0)