Ville Toimela
Blog/Frontend Development

High-endanimationwithouttheperformancetax

PublishedJune 24, 2026
Read time3 min read
TopicFrontend Development
Smooth scroll, scroll-triggered choreography and a WebGL background, on a Next.js site that still scores a perfect 100. Here is the front-end stack I reach for, and the rules that keep it fast.

Most 'award-grade' websites make a quiet trade: they look incredible and load like treacle. A heavy animation library, a WebGL canvas, a hundred scroll listeners, and suddenly the thing janks on a mid-range phone and the Core Web Vitals turn red. I spent years building animated sites at a marketing agency, and the whole game is refusing that trade. This is the stack I use to get motion and speed at the same time, the same one running under this very site.

The trap: motion that tanks your Core Web Vitals

Animation is expensive in three specific ways: layout thrash (animating properties that force the browser to recalculate the page), main-thread blocking (JavaScript running on every frame), and shipping megabytes of library code the user pays for before anything even moves. Every choice below is really about avoiding one of those three.

Smooth scroll with Lenis, not scroll-jacking

Lenis gives you that buttery, weighted scroll feel by interpolating the scroll position, without hijacking it: the scrollbar still works, keyboard and accessibility still work, and you can switch it off entirely for people who ask for reduced motion. It is a few kilobytes, and it quietly drives everything else, because once your scroll position is a smooth value you can hang animations off it cleanly.

GSAP and ScrollTrigger for choreography

For anything tied to scroll position (pinned sections, reveals that fire once, elements that move at their own pace) GSAP with ScrollTrigger is still the most reliable tool there is. The key discipline is animating only transform and opacity. Those two run on the GPU and never trigger layout, so a heading can slide and fade at 60fps while the rest of the page stays calm. The moment you animate width, height or top, you have lost.

Framer Motion for component-level motion

Inside React, Framer Motion handles the smaller, stateful stuff: menu transitions, hover states, mount and exit animations. It thinks in components and plays nicely with Next.js. I reach for GSAP when motion is choreographed to scroll, and Framer when the motion belongs to a component's own lifecycle. Using each for what it is genuinely good at keeps both bundles small.

The rules I never break

  • Animate only transform and opacity. If a property triggers layout, find another way.
  • Reveal on scroll with an IntersectionObserver, once, then disconnect. Do not keep observers running for content nobody is looking at.
  • Respect prefers-reduced-motion. Motion is a finish, not a requirement, and some users need it off.
  • Lazy-load the heavy pieces. A WebGL background or a big animation should never block first paint.
  • Measure on a real mid-range phone, not your laptop. That is where the frame budget actually bites.

None of this is exotic. It is just refusing to treat animation and performance as opposites. A custom WebGL shader background sounds heavy, but written as a single fragment shader with no 3D dependency it costs less than most hero images. It is the discipline, not the restraint, that lets a site feel alive and still load instantly.

Why this stack, still

Next.js for structure and rendering, Lenis for scroll, GSAP for choreography, Framer Motion for components, and a hand-written shader when a page needs a soul. Every piece earns its place, and every piece is chosen to stay off the critical path. That is how you ship something that wins on both feel and Lighthouse, instead of having to pick one.

Next articleFrom demo to production: what building real AI chatbots takes