The animation that would not behave
I thought this would be a weekend project. A nice scroll-driven animation for a case study page. A little storytelling while you scroll. Some panels, some motion, nothing crazy.
It took four full rewrites before I stopped hating it.
The funny part is that the final version is dead simple. Almost boring. Everything before that was clever and fragile. So this is the build log of how I got there.
What I actually wanted to build
The page was a timeline of a product build. I wanted a vertical layout where each section filled the viewport. As you scroll, a mockup on the right should animate through states. Think of it as a poor man’s scrollytelling.
Roughly:
- Left side: text sections stacked vertically.
- Right side: a sticky viewport mockup that changes state based on scroll position.
- No hard jumps. Smooth transitions between states.
It felt like the perfect excuse to lean on modern CSS. I wanted as little JavaScript as possible. Maybe none.
Attempt 1: The pure JavaScript control freak
My first instinct was the old script-kid reflex. Grab scroll position, map it to a progress value, feed that into everything.
I started with something like this:
const sections = [...document.querySelectorAll('[data-step]')];
const viewport = document.querySelector('.viewport');
function onScroll() {
const scrollY = window.scrollY;
const docHeight = document.body.scrollHeight - window.innerHeight;
const progress = scrollY / docHeight;
viewport.style.setProperty('--progress', progress);
let currentIndex = 0;
sections.forEach((section, index) => {
const rect = section.getBoundingClientRect();
if (rect.top < window.innerHeight * 0.5) {
currentIndex = index;
}
});
viewport.dataset.step = currentIndex;
}
window.addEventListener('scroll', onScroll);
onScroll();Then I wired everything in CSS using custom properties and attribute selectors.
Subscribe to continue reading