javascripthtmlreactjsnext.jsframer-motion

Framer motion whileInView is not animating the element while it is in view in nextjs


I am working on a Next.js 14 project using the App Router, and I'm experimenting with animations using Framer Motion. I have a page in my app where I want text elements to animate into view when they come into the viewport. The animation should reveal the text from hidden to visible while a green line moves from left to right simultaneously.

// pages/text-reveal.tsx
import { Reveal } from "@/components/Reveal";

export default function TextRevealPage() {
  return (
    <>
      <div className="min-h-screen gap-[100vh] pt-[50vh] pb-[50vh]  flex flex-col justify-center items-center">
        <Reveal>
          <p className="max-w-sm">
            Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatum
            doloribus excepturi recusandae accusamus ea placeat officiis, natus
            enim aliquam hic!
          </p>
        </Reveal>

        <Reveal>
          <p className="max-w-sm">some short content</p>
        </Reveal>

        <Reveal>
          <p className="max-w-sm">
            Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatum
            doloribus excepturi recusandae accusamus ea placeat officiis, natus
            enim aliquam hic!
          </p>
        </Reveal>
      </div>
    </>
  );
}

And here is the Reveal component that performs the animation:

// components/Reveal.tsx
"use client";
import { motion } from "framer-motion";

type Props = {
  children: JSX.Element;
};

export function Reveal({ children }: Props) {
  return (
    <div
      style={{
        position: "relative",
        width: "fill-content",
        overflow: "hidden",
      }}
    >
      <motion.div
        variants={{
          hidden: { opacity: 0, y: 75 },
          visible: { opacity: 1, y: 0 },
        }}
        initial="hidden"
        whileInView="visible"
        viewport={{ once: true }}
        transition={{ duration: 0.5, delay: 0.25 }}
      >
        {children}
      </motion.div>

      <motion.div
        variants={{
          hidden: { left: 0 },
          visible: { left: "100%" },
        }}
        initial="hidden"
        whileInView="visible"
        viewport={{ once: true }}
        transition={{ duration: 0.5, ease: "easeIn" }}
        style={{
          position: "absolute",
          top: 4,
          bottom: 4,
          left: 0,
          right: 0,
          background: "green",
          zIndex: 20,
        }}
      ></motion.div>
    </div>
  );
}

The Problem:

When I scroll the elements into view in my browser, the animations work for the first and last <p> tags that have larger text inside, but the <p> tag that has short text only shows the green line animation, and the text content itself is not visible. Also when overflow: hidden is active, the <p> with shorter text remains hidden. Removing overflow: hidden makes the text visible but ruins the animation making it less smooth and not as visually appealing.

HTML Output:

Here is the relevant HTML from the browser:

<div class="min-h-screen gap-[100vh] pt-[50vh] pb-[50vh] flex flex-col justify-center items-center">
    <div style="position: relative; width: fill-content; overflow: hidden;">
        <div style="opacity: 1; transform: none;"><p class="max-w-sm">Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatum doloribus excepturi recusandae accusamus ea placeat officiis, natus enim aliquam hic!</p></div>
        <div style="position: absolute; inset: 4px 0px 4px 100%; background: green; z-index: 20;"></div>
    </div>
    <div style="position: relative; width: fill-content; overflow: hidden;">
        <div style="opacity: 0; transform: translateY(75px) translateZ(0);"><p class="max-w-sm">some short content</p></div>
        <div style="position: absolute; inset: 4px 0px 4px 100%; background: green; z-index: 20;"></div>
    </div>
    <div style="position: relative; width: fill-content; overflow: hidden;">
        <div style="opacity: 1; transform: none;"><p class="max-w-sm">Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatum doloribus excepturi recusandae accusamus ea placeat officiis, natus enim aliquam hic!</p></div>
        <div style="position: absolute; inset: 4px 0px 4px 100%; background: green; z-index: 20;"></div>
    </div>
</div>

As you can see the wrapping div on the short text paragraph somehow cant detect that it is in view of the browser so the animation is not triggered:

<div style="opacity: 0; transform: translateY(75px) translateZ(0);">
    <p class="max-w-sm">some short content</p>
</div>

Solution

  • Because of the overflow: hidden when the element is completely out, it's not considered to be inView:

    enter image description here

    In the longer texts, part of the text is inside the not overflown area, so it is considered to be inView:

    enter image description here

    So, one quick fix is to just remove the overflow: hidden, the animation will look different though...

    Maybe you can keep something like the original behaviour by using key frames.