Card Stack Animations on Scroll with Framer Motion and Tailwind CSS

Planted on
Last tended on

Status: #seedling

#react #animations #tutorial

Collapse cards on scroll

This is the classic animation that a Google search for "card stacking animation on scroll" would provide you, and it looks like this:

card_stack_scroll.gif
Source: https://blog.olivierlarose.com/tutorials/cards-parallax

The cited tutorial is a great resources, implementing the useScroll and useTransform hooks from Framer to create this effect.

Reveal cards on scroll

For the desired animation I had to implement at work, the outcome was the one from above - except reversed. You start from the stacked cards, and by scrolling down, the cards stacked below are revealed one by one on scroll:

red-animation.gif

Similar to the scrolling animation from before, we employ the framer hooks to control the rising, scale and opacity values for the 2nd and 3rd card. The first card does not need to have any transformations, just the z-index CSS value needs to be bigger than the one of the other cards.

const { scrollYProgress } = useScroll({
	target: containerRef,
	offset: ['0 0.1', 'end end'],
});

const card2Y = useTransform(scrollYProgress, [0, 0.35], ['3%', '108%']);
const card2Scale = useTransform(scrollYProgress, [0.1, 0.4, 0.5], [0.92, 0.92, 1]);

const card3Y = useTransform(scrollYProgress, [0, 0.4, 0.92], ['100%', '70%', '215%']);
const card3Scale = useTransform(scrollYProgress, [0.4, 0.9, 1], [0.92, 0.92, 1]);
const card2Opacity = useTransform(scrollYProgress, [0, 0.5, 0.55], [0, 0, 1]);

I had to test out multiple value mappings based on the card heights and the scrolling container. It was tricky to create a decent scrolling reveal, and still it's not the smoothest one.

In my situation, at least it was convenient that the heights of the container and cards were fixed:

<div 
	className="flex justify-center bg-black relative min-h-[1880px]"
	ref={containerRef}
>
	<div className="relative flex flex-col gap-8">
		<div className="z-[3] origin-bottom">
			<div className="h-[500px] bg-white rounded-2xl transition-all duration-300" />
		</div>

		<motion.div
			className="absolute top-0 right-0 left-0 z-[2] origin-bottom"
			style={{
				y: card2Y,
				scale: card2Scale,
			}}
		>								
			<div className="h-[500px] bg-primary rounded-2xl transition-all duration-300" />
		</motion.div>

		<motion.div
			className="absolute top-0 right-0 left-0 z-[1] origin-bottom"
			style={{
				y: card3Y,
				scale: card3Scale,
				opacity: card3Opacity,
			}}
		>					
			<div className="h-[500px] bg-white rounded-2xl transition-all duration-300" />
		</motion.div>
	</div>
</div>

Sources