WebGL Fluid Smoke Button
See the Pen WebGL Fluid Smoke Button.
Tech & Dependencies
Features
- ✓ Procedural Generation
- ✓ Fragment Shader
- ✓ Fluid Animation
- ✓ Noise Algorithms
Browser Support
Core
This WebGL Fluid Smoke Button elevates standard UI interactions by embedding a real-time generative shader directly into the interface. Instead of using a heavy video file, it calculates complex fluid dynamics on the fly using Fractional Brownian Motion (fBM). The result is a mesmerizing, never-repeating smoky background that reacts to hover states with a smooth scale and rotation effect, styled effortlessly via Tailwind CSS.
Core Technique
The visual magic relies entirely on a GLSL Fragment Shader. The shader implements a technique called Domain Warping.
- FBM Noise: It layers multiple frequencies of noise (
noisefunction) to create the cloud-like texture. - Domain Warping: Instead of just drawing the noise, the code uses the noise value to distort the coordinate system of another noise calculation. This
fbm(uv + r)logic creates the swirling, fluid motion characteristic of smoke or marble. - Wrapper Library: The JavaScript uses a lightweight helper,
FragmentCanvas, to handle the WebGL boilerplate (creating the context, compiling shaders, and updating theiTimeuniform).
Customization
To change the aesthetics, you need to modify the GLSL code inside the fragmentShader string. The colors are defined using mix() functions near the end of the main() function.
void main(void) {
// ... calculation of 'f' ...
// Base dark color
vec3 color = mix(
vec3(0.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.5), // Change to Green
clamp((f * f) * 6.0, 0.0, 5.0)
);
// Secondary highlight
color = mix(
color,
vec3(1.0, 0.2, 0.0), // Change Magenta to Orange
clamp(length(q) * length(q), 0.0, 1.0)
);
// ...
}
To adjust the speed of the animation, modify the uniforms object in the JavaScript:
new FragmentCanvas(canvas, {
fragmentShader,
uniforms: {
// Divide by a smaller number to speed up
offset: (gl, location, time) =>
gl.uniform1f(location, Math.cos(time / 500) * 1.0)
}
});
Tips
1. Performance Impact: Running a complex fragment shader (calculating noise per pixel) consumes GPU resources. While fine for a single “Hero” button, avoid using this on list items or grid elements (e.g., 20+ buttons at once), as it will significantly drain battery on mobile devices.
2. Handling Resize: The snippet includes a basic resize handler. In a production environment (especially React/Vue), ensure you disconnect the resize listener or destroy the WebGL context when the component unmounts to prevent memory leaks.


