<main>
	<article>
		<h2>So...</h2>
		<h3>We need to talk</h3>
	</article>
	<article>
		<h2>I started this thing</h2>
	</article>
	<article>
		<h2>It's called</h2>
	</article>
	<article>
		<h2><a href="https://webanimation.blog">webanimation.blog</a></h2>
	</article>
</main>
main {
	scroll-snap-type: y mandatory;
	position: relative;
	max-height: 100vh;
	overflow-y: auto;
}

article {
	height: 100vh;
	width: 100vw;

	display: flex;
	flex-flow: column nowrap;
	justify-content: center;
	align-items: center;

	scroll-snap-align: start;
}

article:nth-of-type(1) {
	background-color: rgba(0, 82, 178, 1);
}
article:nth-of-type(2) {
	background-color: rgba(255, 164, 0, 1);
}
article:nth-of-type(3) {
	background-color: rgba(144, 195, 255, 1);
}
article:nth-of-type(4) {
	background-color: rgba(255, 173, 25, 1);
}

h2,
h3, a {
	font: 400 3rem/1.5 sans-serif;
	color: white;
	transform-origin: center;
	text-decoration: none;
}

h3 {
	font: 400 2em/1.5 sans-serif;
}
View Compiled
gsap.set("main article *", { autoAlpha: 0, y: "1rem" });

const animateVisible = (block, ratio, isIntersecting) => {
	if (ratio > 0 && isIntersecting) {
		gsap.to(block.querySelectorAll("*"), {
			duration: 1,
			autoAlpha: 1,
			y: "0",
			stagger: 0.3,
			ease: "power3.inOut"
		});
	} else {
		gsap.set(block.querySelectorAll("*"), {
			autoAlpha: 0,
			y: "1rem"
		});
	}
};

const blocks = document.querySelectorAll("main article");

const blocksObserver = new IntersectionObserver(
	(entries) => {
		return entries.forEach((event) => {
			const { target, intersectionRatio, isIntersecting } = event;
			animateVisible(target, intersectionRatio, isIntersecting);
		});
	},
	{ threshold: 0.5 }
);

for (const block of blocks) {
	blocksObserver.observe(block);
}
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js