Skip to main content
The Grainient component creates stunning animated gradient backgrounds using WebGL shaders, featuring customizable colors, warp effects, grain texture, and color adjustments.

Overview

Key features:
  • WebGL 2.0 shader-based rendering
  • Three-color gradient blending
  • Animated warp/distortion effects
  • Grain texture overlay
  • Color correction (contrast, gamma, saturation)
  • Center offset and zoom controls
  • Responsive and performant
  • Fully customizable via props

Props

Animation

timeSpeed
number
default:"0.25"
Speed of the animation (higher = faster)

Colors

color1
string
default:"#FF9FFC"
First gradient color (hex format)
color2
string
default:"#5227FF"
Second gradient color (hex format)
color3
string
default:"#B19EEF"
Third gradient color (hex format)
colorBalance
number
default:"0.0"
Balance between colors (-1 to 1)

Warp Effects

warpStrength
number
default:"1.0"
Overall strength of warp effect
warpFrequency
number
default:"5.0"
Frequency of warp waves
warpSpeed
number
default:"2.0"
Speed of warp animation
warpAmplitude
number
default:"50.0"
Amplitude of warp displacement

Blending

blendAngle
number
default:"0.0"
Angle of gradient blend in degrees
blendSoftness
number
default:"0.05"
Softness of color transitions

Rotation & Noise

rotationAmount
number
default:"500.0"
Amount of rotation effect
noiseScale
number
default:"2.0"
Scale of noise pattern

Grain

grainAmount
number
default:"0.1"
Amount of grain texture (0-1)
grainScale
number
default:"2.0"
Scale of grain pattern
grainAnimated
boolean
default:"false"
Whether grain moves over time

Color Correction

contrast
number
default:"1.5"
Contrast adjustment (1 = normal)
gamma
number
default:"1.0"
Gamma correction (1 = normal)
saturation
number
default:"1.0"
Color saturation (0 = grayscale, 1 = normal)

Transform

centerX
number
default:"0.0"
Horizontal center offset (-1 to 1)
centerY
number
default:"0.0"
Vertical center offset (-1 to 1)
zoom
number
default:"0.9"
Zoom level (lower = zoomed in)

Styling

className
string
default:""
Additional CSS classes for the container

Implementation

import Grainient from './components/Grainient/Grainient';

function App() {
  return (
    <div className="w-full h-screen">
      <Grainient
        color1="#FF9FFC"
        color2="#5227FF"
        color3="#B19EEF"
        timeSpeed={0.5}
        warpStrength={1.5}
        grainAmount={0.15}
        grainAnimated
      />
    </div>
  );
}

Example Configurations

<Grainient
  color1="#000000"
  color2="#726e6e"
  color3="#fafafa"
  timeSpeed={0.9}
  colorBalance={0}
  warpStrength={1}
  warpFrequency={2}
  warpSpeed={2}
  warpAmplitude={50}
  blendAngle={-20}
  blendSoftness={0.05}
  rotationAmount={500}
  noiseScale={2}
  grainAmount={0.1}
  grainScale={2}
  grainAnimated
  contrast={1.5}
  gamma={1}
  saturation={1}
  centerX={0}
  centerY={0}
  zoom={0.9}
/>
Monochromatic gradient with subtle animation (used in Hero component)

Shader Architecture

The component uses custom GLSL shaders:

Vertex Shader

#version 300 es
in vec2 position;
void main() {
  gl_Position = vec4(position, 0.0, 1.0);
}
Simple fullscreen triangle rendering.

Fragment Shader

The fragment shader implements:
  1. UV Transformation: Centers and zooms the texture coordinates
  2. Noise-based Rotation: Applies dynamic rotation based on noise
  3. Warp Effect: Distorts UVs with sine waves
  4. Color Blending: Mixes three colors with smoothstep
  5. Grain Overlay: Adds film grain texture
  6. Color Correction: Applies contrast, saturation, and gamma
Key shader code:
// Warp effect
tuv.x += sin(tuv.y * frequency + warpTime) / amplitude;
tuv.y += sin(tuv.x * (frequency * 1.5) + warpTime) / (amplitude * 0.5);

// Gradient blending
vec3 layer1 = mix(colDark, colOrg, smoothstep(edge0, edge1, blendX));
vec3 layer2 = mix(colOrg, colLav, smoothstep(edge0, edge1, blendX));
vec3 col = mix(layer1, layer2, smoothstep(v0, v1, tuv.y));

// Grain
float grain = fract(sin(dot(grainUv, vec2(12.9898, 78.233))) * 43758.5453);
col += (grain - 0.5) * uGrainAmount;

Color Conversion

The component converts hex colors to RGB:
const hexToRgb = hex => {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  if (!result) return [1, 1, 1];
  return [
    parseInt(result[1], 16) / 255, 
    parseInt(result[2], 16) / 255, 
    parseInt(result[3], 16) / 255
  ];
};

WebGL Initialization

const renderer = new Renderer({
  webgl: 2,               // Use WebGL 2.0
  alpha: true,            // Transparent background
  antialias: false,       // Disabled for performance
  dpr: Math.min(window.devicePixelRatio || 1, 2) // Cap at 2x
});

Responsive Behavior

The component uses ResizeObserver for responsive sizing:
const setSize = () => {
  const rect = container.getBoundingClientRect();
  const width = Math.max(1, Math.floor(rect.width));
  const height = Math.max(1, Math.floor(rect.height));
  renderer.setSize(width, height);
  res[0] = gl.drawingBufferWidth;
  res[1] = gl.drawingBufferHeight;
};

const ro = new ResizeObserver(setSize);
ro.observe(container);

Animation Loop

const t0 = performance.now();
const loop = t => {
  program.uniforms.iTime.value = (t - t0) * 0.001;
  renderer.render({ scene: mesh });
  raf = requestAnimationFrame(loop);
};

Cleanup

useEffect(() => {
  // ... initialization
  
  return () => {
    cancelAnimationFrame(raf);
    ro.disconnect();
    try {
      container.removeChild(canvas);
    } catch {
      // Ignore
    }
  };
}, [/* all props */]);
The Grainient component re-initializes when any prop changes. For performance, avoid changing props frequently during runtime.

Performance Considerations

  • Device pixel ratio capped at 2x to prevent excessive GPU load
  • Antialias disabled for better performance
  • WebGL 2.0 for better shader performance
  • Efficient fullscreen triangle rendering (3 vertices vs 4 for quad)
  • ResizeObserver for optimal resize handling

Browser Compatibility

Requires:
  • WebGL 2.0 support
  • ES6+ JavaScript features
  • Modern browser (Chrome 56+, Firefox 51+, Safari 15+, Edge 79+)

Build docs developers (and LLMs) love