reactjsframer-motion

how to prevent the useInView() and useScroll() framer-motion hooks from start together at the same time?


Description of the wanted animation behaviour: when a part of the section is inside the viewport, the element I want to animate should ENTER the viewport using a vertical movement from its initial position to the final position (in this case from y= 300 to y=50). Then, for the leaving/exit animation, I want it to happen along with the scrolling, the element should move toward the right side of the screen leaving the viewport completely.

The actual behaviour:

the order of the animation to be first the inView, which is the element coming from y=300 to y=50 then start moving to the right direction until it disappears from the view while scrolling down the page, but the way it works, when the black background section comes into the viewport the purple background element position starts from the right side of the screen, and moving to the left side in scrolling, and stick until the scroll reaches the end of the black background section

here is my implementation:

import { motion, useInView, useScroll, useTransform } from "framer-motion";
import { useRef } from "react";
import { SectionHeading } from "../UI/Headings";
import { Section } from "../UI/Section";

export default function About() {
  const inViewRef = useRef();
  useInView(inViewRef, { margin: "-180px 0px", once: true });

  const scrollRef = useRef(null);
  const { scrollYProgress } = useScroll({ target: scrollRef});
  const x = useTransform(scrollYProgress, [0, 1], ["0", "120%"]);

  return (
    <Section ref={inViewRef} id="who-i-am" className="bg-black h-[150svh]">
        <motion.div className="h-[150svh]"
        initial={{y: 300, visibility: "hidden"}}
        whileInView={{ y: 50, visibility: "visible" }} 
        // because here the y=50, when the user scrolls to the end of the section 
        // (which is the black bg, th purple container will go exceeds the section end by 50px,
        // to go around this I had to add another 50px to the section height).
        transition={{ duration: 1.6, ease: "easeIn",  type:"spring" }}>
          <motion.div ref={scrollRef} style={{x}} className="bg-purple-500 sticky top-0">
          <SectionHeading>SECTION HEADING</SectionHeading>
          <p>
            Lorem ipsum dolor, sit amet consectetur adipisicing elit. Temporibus aliquam veniam non quod veritatis sed ex ipsam doloribus. Ratione est quae quo architecto. Labore, quisquam. Ipsam vitae libero veniam eius, pariatur magni perferendis, molestiae numquam, iure dolorum voluptatum nemo? Illum laudantium quae voluptatem reiciendis hic at aut quam ea error.
          </p>
          <p>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Vitae recusandae atque quibusdam ipsa ab fugit minus, veritatis earum molestias repellat.
          </p>
          </motion.div>
        </motion.div>
    </Section>
  );
}

what I tried is to play with the offset option: useScroll({ target: scrollRef, offset:["center center"]}) to strict its starting and ending timing made it look like there is no scrolling effect at all, also tried to set a condition to make the horizontal motion happens only after inView:

<motion.div ref={scrollRef} style={isInView ? { x } : {}} className="bg-purple-500 sticky top-0">

Solution

  • To achieve the desired animation behavior, you can use a combination of the useInView and useScroll hooks from Framer Motion. Here's how you can modify your code:

    import { motion, useInView, useScroll, useTransform } from "framer-motion";
    import { useRef } from "react";
    import { SectionHeading } from "../UI/Headings";
    import { Section } from "../UI/Section";
    
    export default function About() {
      const inViewRef = useRef();
      const isInView = useInView(inViewRef, { margin: "-180px 0px", once: true });
    
      const scrollRef = useRef(null);
      const { scrollYProgress } = useScroll({ target: scrollRef });
      const x = useTransform(scrollYProgress, [0, 1], ["0%", "120%"]);
    
      return (
        <Section ref={inViewRef} id="who-i-am" className="bg-black h-[150svh]">
          <motion.div
            className="bg-purple-500 sticky top-0"
            initial={{ y: 300, visibility: "hidden" }}
            animate={isInView ? { y: 50, visibility: "visible" } : {}}
            transition={{ duration: 1.6, ease: "easeIn", type: "spring" }}
          >
            <motion.div ref={scrollRef} style={{ x }} className="h-[150svh]">
              <SectionHeading>SECTION HEADING</SectionHeading>
              <p>
                Lorem ipsum dolor, sit amet consectetur adipisicing elit. Temporibus
                aliquam veniam non quod veritatis sed ex ipsam doloribus. Ratione
                est quae quo architecto. Labore, quisquam. Ipsam vitae libero veniam
                eius, pariatur magni perferendis, molestiae numquam, iure dolorum
                voluptatum nemo? Illum laudantium quae voluptatem reiciendis hic at
                aut quam ea error.
              </p>
              <p>
                Lorem ipsum dolor sit amet, consectetur adipisicing elit. Vitae
                recusandae atque quibusdam ipsa ab fugit minus, veritatis earum
                molestias repellat.
              </p>
            </motion.div>
          </motion.div>
        </Section>
      );
    }
    

    This is all the modifications I can think of, and if it still doesn't work, there's nothing I can do