framer-motion

Framer animation staggerd animation not working as exspected


I am using framer motion to create a staggerd effect where 2 images slide in from the left.

It works perfect if im in the correct view port and refresh the screen.

However, if I scroll down to the elements, if I scroll slowly the first image willanimate, and the second won't untill I scroll down a bit further.

I think the bug is because there not in the same viewport at the same time.

How can I fix this by using a wrapper container or something?

"use client";

import { motion } from "framer-motion";

const variants = {
  initial: {
    opacity: 0,
    transform: 'translateX(-150px)',
    // filter: 'blur(5px)'
  },
  animate: (index: number) => ({
    opacity: 1,
    transform: 'translateX(0px)',
    // filter: 'blur(0px)',
    transition: {
      duration: 1,
      ease: 'easeInOut',
      delay: 0.3 * index,
    }
  })
};

const FramerAnimationSlideIn = ({ items }: any) => {
  return (
    // @ts-ignore
    items.map((item, index) => (
      <motion.div
        variants={variants}
        initial="initial"
        whileInView="animate"
        key={item.src}
        viewport={{
          once: true,
        }}
        custom={index}
      >
        {item}
      </motion.div>
    )))
}

export default FramerAnimationSlideIn;

Solution

  • Because you have the whileInView prop on each item element, the elements wil animate individually, and only when the element is in view. You are correct in that you need to add a wrapping div to use as the viewport trigger so all the elemet's animations fire at the same time.

    Framer Motion provides a isInView hook that can be used to help in this situation. When the container element is in view, we can tell each of the elemets to animate:

    const FramerAnimationSlideIn = ({ items }: { items: Array<any> }) => {
      const ref = useRef(null);
      const isInView = useInView(ref, { once: true });
    
      return (
        <div ref={ref}>
          {items.map((item, index) => (
            <motion.div
              key={index}
              variants={variants}
              initial="initial"
              // Animate when the container is in view
              animate={isInView && 'animate'}
              custom={index}
            >
              {item}
            </motion.div>
          ))}
        </div>
      );
    };
    

    You can customize the behavior with different options, similarly to how you would change the viewport prop on motion elements.

    Here is a working example.