1

I'm trying to assign the same animation to multiple instances of a component, using Framer Motion and the react-intersection-observer package

import { useEffect, useRef, useCallback } from "react";

import { motion, useAnimation } from "framer-motion";
import { useInView } from "react-intersection-observer";


const levels = [
    {
        title: "GROUP LESSONS",
        description:
            "Lorem ipsum",
    },
    {
        title: "WORKSHOPS",
        description:
            "Lorem ipsum",
    },

];

const container = {
    show: {
        transition: {
            staggerChildren: 0.2,
        },
    },
};

const item = {
    hidden: { opacity: 0, x: 200 },
    show: {
        opacity: 1,
        x: 0,
        transition: {
            ease: [0.6, 0.01, -0.05, 0.95],
            duration: 1.6,
        },
    },
};



const Levels = () => {
        const animation = useAnimation();
        const [levelRef, inView] = useInView({
        triggerOnce: true,
     });

   useEffect(() => {
        if (inView) {
        animation.start("show");
      }
   }, [animation, inView]);


    return (
        <LevelsContainer>
            {levels.map((level, index) => {
                return (
                    <LevelsWrapper
                        key={index}
                        ref={levelRef}
                        animate={animation}
                        initial="hidden"
                        variants={container}
                    >
                        <Level variants={item}>
                            <Title>{level.title}</Title>
                            <Description>{level.description}</Description>
                        </Level>
                    </LevelsWrapper>
                );
            })}
        </LevelsContainer>
    );
};

This results in the animation loading only when scrolling to the last LevelWrapper component. Then "inView" is set to true and all the components animate at the same time. In the react-intersection-observer package documentation, there's some info about wrapping multiple ref assignments in a single useCallback, so I've tried that:

const animation = useAnimation();
    const ref = useRef();
    const [levelRef, inView] = useInView({
        triggerOnce: true,
    });

    const setRefs = useCallback(
        (node) => {
            ref.current = node;
            levelRef(node);
        },
        [levelRef]
    );

    useEffect(() => {
        if (inView) {
            animation.start("show");
        }
    }, [animation, inView]);

    return (
        <LevelsContainer>
            {levels.map((level, index) => {
                return (
                    <LevelsWrapper
                        key={index}
                        ref={setRefs}
                        animate={animation}
                        initial="hidden"
                        variants={container}
                    >
                        <Level variants={item}>
                            <Title>{level.title}</Title>
                            <Description>{level.description}</Description>
                        </Level>
                    </LevelsWrapper>
                );
            })}
        </LevelsContainer>
    );

But the animations still don't trigger individually for each LevelWrapper component. What's happening?

MrFacundo
  • 167
  • 3
  • 13

1 Answers1

2

No idea why the code in the question doesn't work but I found the final result can be reached without using neither useEffect, useRef, useCallback, , useAnimation or useInView.

In the Framer Motion documentation:

Motion extends the basic set of event listeners provided by React with a simple yet powerful set of UI gesture recognisers.

It currently has support for hover, tap, pan, viewport and drag gesture detection. Each gesture has a series of event listeners that you can attach to your motion component.

Then applied whats explained here: https://www.framer.com/docs/gestures/#viewport-options

            <LevelsWrapper
                key={index}
                initial="hidden"
                whileInView="show"
                variants={container}
                viewport={{ once: true, amount: 0.8, margin: "200px" }}
            >
MrFacundo
  • 167
  • 3
  • 13