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",
});
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.