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>
);
}
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.
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>
Because of the overflow: hidden
when the element is completely out, it's not considered to be inView
:
In the longer texts, part of the text is inside the not overflown area, so it is considered to be inView
:
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.