Adding a Gooey Menu Hover Effect with GSAP

Leroy · 1 Jun 2026 · 2 min read

The Idea

I wanted the navigation menu on this blog to feel alive. When you hover over a link, a blob stretches and slides to it with a liquid-like motion. Like honey or warm gum. Not just a color change or underline, but something tactile and playful.

The Technique

The effect combines two things.

  1. GSAP (GreenSock Animation Platform) handles the smooth tweening of the blob's position and width with power2.out easing for that deceleration feel.
  2. An SVG gooey filter (feGaussianBlur + feColorMatrix) creates the liquid stretch effect where the blob appears to ooze between links.

How It Works

When you hover over a nav link, a small JavaScript function measures the link's position and size using getBoundingClientRect(), then tells GSAP to animate the blob to those coordinates:

function moveBlob(target) {
  var rect = target.getBoundingClientRect();
  var navRect = nav.getBoundingClientRect();
  gsap.to(blob, {
    left: rect.left - navRect.left - 8,
    width: rect.width + 16,
    opacity: 1,
    duration: 0.4,
    ease: 'power2.out'
  });
}

The SVG filter is the key. It lives as an inline SVG in the page body:

<svg style="position:absolute;width:0;height:0;" aria-hidden="true">
  <defs>
    <filter id="goo">
      <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
      <feColorMatrix in="blur" mode="matrix"
        values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7" result="goo" />
      <feBlend in="SourceGraphic" in2="goo" />
    </filter>
  </defs>
</svg>

feGaussianBlur softens the edges of the blob. feColorMatrix sharpens them back with a threshold. That creates the gooey merging effect where the blob appears to flow.

Why GSAP from CDN?

This project has no JavaScript bundler. It is a Go server that serves static files. Adding webpack or esbuild just for one animation felt like overkill. GSAP's CDN is fast, version-pinned, and about 30KB gzipped. No build step.

Accessibility

  • prefers-reduced-motion: the animation disables itself if the user has this preference set.
  • Keyboard navigation: the blob follows tab focus, not just mouse hover.
  • Touch devices: the animation is disabled on touch screens to avoid flickering before page navigation.
  • Screen readers: the blob element has aria-hidden="true" and the SVG filter is visually hidden.

Dark Mode Compatibility

The blob uses the theme's --accent CSS variable at 30% opacity via color-mix(). It adapts to both light and dark themes without extra code.

The Result

Hover over the navigation links at the top of this page. The blob slides with a satisfying deceleration, stretching between links that are close together. A small touch that makes the site feel more polished.

related posts

01 Jun
Adding a Gooey Menu Hover Effect with GSAP
How I added a viscous, liquid-like gooey hover effect to the navigation menu using GSAP and an SVG filter - no JavaScript framework required.
01 Jun
Bouncy Card Hover: A Tiny CSS Detail That Makes a Difference
How a single CSS class with a spring-like cubic-bezier adds a satisfying bounce to every card on this blog - and why small animations matter.
01 Jun
Tailwind CSS: Why I Use a Utility-First Framework for This Blog
How Tailwind CSS v4 powers this blog's design - from the Catppuccin-inspired dark mode to the responsive card layout - and why utility-first CSS wins for me.