← Blog
Abstract glowing neon streaks against a dark background
Photo by Logan Voss on Unsplash

Building the special effects: shaders, scroll-linked motion, and the other 10%

September 21, 2026

Structure, content and layout are most of any site — call it 90% of the work, and it's the part every competent build gets right. The other 10% is the handful of specific effects a visitor actually remembers, and it's small enough to break down effect by effect. Here are three we see constantly in captured sites, with working code for each.

Film-grain / noise overlay

A cheap, full-screen noise shader blended over the page reads as "cinematic" almost instantly — it's one of the highest ratio-of-effort- to-effect tricks available. A single fragment shader, rendered to a fullscreen quad on top of everything else with a screen or overlay blend mode:

uniform float uTime;

float rand(vec2 co) {
  return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}

void main() {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  float grain = rand(uv + fract(uTime)) * 0.06;
  gl_FragColor = vec4(vec3(grain), grain);
}

Keep the opacity low — 0.04–0.08 is usually enough to read as texture without visibly dithering the page underneath it.

Magnetic buttons

A button that leans toward the cursor as it approaches, then snaps back on mouseleave. No physics library required — just distance-based transform with a spring-ish ease on the way out:

btn.addEventListener("mousemove", (e) => {
  const r = btn.getBoundingClientRect();
  const x = e.clientX - (r.left + r.width / 2);
  const y = e.clientY - (r.top + r.height / 2);
  gsap.to(btn, { x: x * 0.3, y: y * 0.3, duration: 0.3, ease: "power2.out" });
});
btn.addEventListener("mouseleave", () => {
  gsap.to(btn, { x: 0, y: 0, duration: 0.5, ease: "elastic.out(1, 0.4)" });
});

The elastic.out on the return, not the approach, is what sells it — the button following the cursor should feel damped and precise, the snap-back should feel like it's got a little momentum to shed.

Liquid / SVG morph transitions

GSAP's MorphSVGPlugin interpolating between two path shapes is the mechanism behind most "liquid" logo reveals and page transitions — it's quite literally what our own preloader mark does on this site, morphing between six shapes on load. The trick isn't the plugin call, it's authoring path data with a matching point count and winding order between shapes, or the morph interpolates through garbage geometry along the way.

gsap.to(".mark-path", {
  morphSVG: "#shape-2",
  duration: 0.9,
  ease: "power3.inOut",
});
Performance guardrail: GPU-only, and pause off-screen. Every effect above should animate transform/opacity or write to a shader uniform — never layout-triggering properties. And anything running a per-frame loop (a shader, a physics-ish magnetic effect) should pause via IntersectionObserver the moment it scrolls out of view. A noise shader nobody can see is still costing a frame budget if you let it keep rendering.

None of these effects are hard to build once you know exactly what a site is doing — the hard part is usually figuring out which one it is. That's what reverse_engineer_animations and reverse_engineer_webgl exist to answer directly, instead of guessing from a screen recording.