Fixing GSAP Overlay Flicker in CosmicInterestsSection (Next.js 15)

Root cause and fix for dual-color background flicker and UI flashing in a GSAP + ScrollTrigger overlay with mixed blends and pinning.

September 13, 2025 (3w ago)
7 min read
1,220 views
204 likes
Category
Intermediate
#gsap#scrolltrigger#nextjs#animation#performance#compositing

Fixing GSAP Overlay Flicker in CosmicInterestsSection (Next.js 15)

This post documents how I investigated and fixed a subtle flicker that appeared during the rollback phase of a long-scrolling animation in src/components/CosmicInterestsSection.tsx.

The symptom: after the interest card animation completes, the dual‑color background occasionally flickered, sometimes even flashing the nav bar and making the cards briefly disappear.

What caused the flicker?

The issue came from a combination of compositing and stacking context churn:

  • Nested alpha fades: Both the overlay container (overlayRef) and its child .black-overlay were animating opacity during the tail/rollback. When a parent and child change alpha simultaneously, browsers can promote/demote layers and re-blend large surfaces frame-to-frame, which is prone to flicker.
  • Blend and blur under motion: Cards use gradient overlays with mix-blend-overlay, and the text background had backdrop-blur. While these are beautiful, animating big areas on top at the same time increases the chances of composition “pops”.
  • Pin boundary thrash: Near the ScrollTrigger end, we were toggling overlay visibility and pointer events. Combined with smoothing and pinning, that quick toggling can cause a momentary reflow/repaint that looks like flashing.

How I fixed it

All changes were local to src/components/CosmicInterestsSection.tsx and did not alter the high-level flow.

  • Keep the parent overlay fully opaque: During the tail we stopped fading the overlay container and modulate only the child .black-overlay opacity. This prevents nested alpha composition conflicts.
  • Stabilize compositing: Promoted .black-overlay and the overlay container to their own GPU layers (translateZ(0), backface-visibility: hidden, will-change) and used isolation: isolate on .black-overlay to keep blending predictable.
  • Reduce visibility thrash: Instead of toggling visibility at the boundary, we move the overlay offscreen (yPercent: 100) and disable pointer events, plus a short debounce so we don’t flip states every frame when hovering near the end.
  • Lighten blur under load: The text background blur is kept very light in the initial phase and disabled during heavy composition moments to avoid GPU spikes.
  • Z-index sanity: Lowered the overlay’s z-index so the nav always stays reliably above, eliminating header flashing.

Key snippets

Stop fading the parent; control child only

// Stage 5 tail: keep overlay fully opaque and visible
// src/components/CosmicInterestsSection.tsx (onUpdate tail)
gsap.set(overlayRef.current, { opacity: 1, visibility: "visible" });
 
// Modulate only the black overlay
const blackOverlay = overlayRef.current?.querySelector(".black-overlay");
if (blackOverlay) {
  const blackAlpha = 1 - tailProgress * 0.15; // 1 -> 0.85
  gsap.set(blackOverlay, { opacity: blackAlpha, ease: "none" });
}

Promote layers for stable composition

// .black-overlay is a dedicated compositing layer now
<div
  className="black-overlay absolute inset-0 bg-black z-30 pointer-events-none"
  style={{
    opacity: 0,
    willChange: "opacity",
    contain: "paint",
    backfaceVisibility: "hidden",
    transform: "translateZ(0)",
    isolation: "isolate",
  }}
/>

Avoid visibility flipping at the boundary

// When ScrollTrigger deactivates, debounce hiding to avoid thrash
if (!overlayHideTimeoutRef.current) {
  overlayHideTimeoutRef.current = window.setTimeout(() => {
    gsap.set(overlayRef.current, {
      yPercent: 100,
      pointerEvents: "none",
      opacity: 1,
      ease: "none",
    });
    overlayHideTimeoutRef.current = null;
  }, 80);
}

Results

  • No more dual‑color flicker during rollback.
  • Interest cards and the nav bar remain solid, with no flashes.
  • Animation feel remains intact; only the rendering stability was improved.

If you’re animating complex overlays on top of blended/blurred backgrounds, favor controlling a single opacity source and keep the rest of the stack stable. Promote layers intentionally and avoid rapid visibility/pointer-events toggles near ScrollTrigger boundaries.

Appendix: Tools & Stack

  • Next.js 15 (App Router), React 19
  • GSAP + ScrollTrigger
  • Tailwind CSS
  • Hardware-accelerated compositing hints (translateZ(0), will-change, backface-visibility)
CleanLove

Written by CleanLove

Frontend engineer exploring delightful motion and robust UX

Initializing application
Loading page content, please wait...