Jenaro Calvino

Animate on show with React & React Spring

Recently I've been wanting to learn how to use react-spring and also trying to learn more about typescript. Two days ago I was assigned a task on a project to animate components as the user scrolls into them, you know, fly-in/fade-in/whatever-the-client-wants-this-time, anyways, I thought to myself, this is a great opportunity to check some of the stuff off my list.

Our objective/end-product

I'm going to break bit by bit how I built this how and how to use it:

This is the wrapper component that will be returned to the end user.

const wrapper: FunctionComponent<WrapperProps> = ({
  children,
  style, // use spring return object.
  visible // ref for the element to be watched.
}) => (
  <div ref={visible}>
    <animated.div style={style}>{children}</animated.div>
  </div>
)

Here we define the hook, I removed some types here for readability.

function useAnimateOnShow(from, to, config) {
  const ref = useRef<HTMLElement>()
  const [visible, setVisible] = useState(false)
  const props = useSpring(visible ? to : from)

  useEffect(() => {

    // if no window or no Intersection Observer early return showing the elements.
    if (typeof window === 'undefined') return
    if (
      !('IntersectionObserver' in window) ||
      !('IntersectionObserverEntry' in window) ||
      !('isIntersecting' in window.IntersectionObserverEntry.prototype)
    ) {
      setVisible(true)
      return function cleanup() {
        console.warn('No intersection observer in window.')
      }
    }

    // IO Callback
    const handleVisible = (
      entries: IntersectionObserverEntry[],
      observer: IntersectionObserver
    ) => {
      if (entries.length > 0) {
        const entry = entries[0]

        // if config is infinite then keep watching otherwise unobserve.
        if (config?.infinite) {
          setVisible(entry.isIntersecting)
        } else {
          if (entry.intersectionRatio > 0) {
            setVisible(true)
            observer.unobserve(entry.target)
          }
        }

        // Custom extra method just because.
        if (config?.onShow) {
          config.onShow(entry.isIntersecting)
        }
      }
    }

   // IO Options
    const options = {
      root: config?.root || null,
      rootMargin: config?.rootMargin || '0px',
      threshold: config?.threshold || 1.0
    }

    const observer = new IntersectionObserver(handleVisible, options)

    if (ref && ref.current) {
      observer.observe(ref.current)
    }

    return function cleanup() {}
  }, [ref, config])

  return [wrapper, ref, props]
}

export default useAnimateOnShow

That's it, single file to get our React components animating in like this.

Screen Gif

Got a suggestion or improvement? You can check the repo here

I'll continue to add more and more examples.