A React library for rendering WebGL shaders with a simple, declarative API. Write GLSL fragment shaders and render them as React components with minimal setup. It has no dependencies.
- π¨ Simple declarative API - Render shaders as React components
- π§ Built-in uniforms - Automatic time, resolution, and mouse position tracking
- πΌοΈ Texture support - Load images, videos, and canvas elements as textures
- π Performance monitoring - Built-in FPS and frame time telemetry
- π― TypeScript first - Full type safety and IntelliSense support
- β‘ Optimized rendering - Efficient uniform updates and texture management
- πͺ Composable hooks - Build custom shader components with low-level hooks
- π¦ Zero dependencies - Lightweight and no external runtime dependencies
npm install glsl-reactyarn add glsl-reactpnpm add glsl-reactimport { ShaderCanvas } from 'glsl-react';
const fragmentShader = `
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec3 color = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4));
gl_FragColor = vec4(color, 1.0);
}
`;
function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ShaderCanvas fragmentShader={fragmentShader} />
</div>
);
}The library automatically provides three essential uniforms:
u_resolution(vec2) - Canvas dimensions in pixelsu_time(float) - Time in seconds since shader startedu_mouse(vec2) - Normalized mouse position [0, 1]
Pass custom uniforms to your shader. Important: Always memoize the uniforms object to prevent unnecessary re-renders and performance issues:
import { useMemo } from 'react';
function MyShader() {
const [scale, setScale] = useState(2.0);
const [color, setColor] = useState([1.0, 0.5, 0.2]);
const uniforms = useMemo<Uniforms>(() => ({
u_color: { type: 'vec3', value: color },
u_scale: { type: 'float', value: scale },
u_matrix: { type: 'mat4', value: new Float32Array(16) }
}), [color, scale]);
return <ShaderCanvas fragmentShader={shader} uniforms={uniforms} />;
}Load images, videos, or canvas elements as textures:
import { useMemo, useState, useEffect } from 'react';
function TextureShader() {
const [image, setImage] = useState<HTMLImageElement | null>(null);
useEffect(() => {
const img = new Image();
img.src = '/path/to/texture.jpg';
img.onload = () => setImage(img);
}, []);
const uniforms = useMemo<Uniforms>(() => {
if (!image) return {};
return {
u_texture: {
type: 'texture',
value: image,
options: {
wrapS: TextureWrap.REPEAT,
wrapT: TextureWrap.REPEAT,
minFilter: TextureFilter.LINEAR,
magFilter: TextureFilter.LINEAR,
flipY: true
}
}
};
}, [image]);
if (!image) return <div>Loading...</div>;
return <ShaderCanvas fragmentShader={shader} uniforms={uniforms} />;
}Track shader performance with the built-in telemetry hook:
import { ShaderCanvas, useShaderTelemetry } from 'glsl-react';
function App() {
const { onFrameRender, Telemetry } = useShaderTelemetry({
position: 'top-right'
});
return (
<>
<ShaderCanvas
fragmentShader={shader}
onFrameRender={onFrameRender}
/>
<Telemetry />
</>
);
}Main component for rendering WebGL shaders.
Props:
| Prop | Type | Required | Description |
|---|---|---|---|
fragmentShader |
string |
Yes | GLSL fragment shader source code |
vertexShader |
string |
No | Custom vertex shader (defaults to fullscreen quad) |
uniforms |
Uniforms |
No | Custom uniform values |
pixelRatio |
number |
No | Pixel ratio for high-DPI displays (default: window.devicePixelRatio) |
style |
CSSProperties |
No | CSS styles for canvas element |
className |
string |
No | CSS class name |
options |
ShaderCanvasOptions |
No | Additional configuration options |
onFrameRender |
(frameTime: number) => void |
No | Callback fired after each frame |
Supported uniform types:
- Scalars:
float,int - Vectors:
vec2,vec3,vec4,ivec2,ivec3,ivec4 - Matrices:
mat3,mat4 - Textures:
texture
TextureWrap enum:
REPEAT- Repeat textureCLAMP_TO_EDGE- Clamp to edgeMIRRORED_REPEAT- Mirror and repeat
TextureFilter enum:
NEAREST- Nearest neighbor filteringLINEAR- Linear filteringNEAREST_MIPMAP_NEAREST- Nearest mipmap, nearest filteringLINEAR_MIPMAP_NEAREST- Linear mipmap, nearest filteringNEAREST_MIPMAP_LINEAR- Nearest mipmap, linear filteringLINEAR_MIPMAP_LINEAR- Linear mipmap, linear filtering
Creates performance monitoring for shader rendering.
const { onFrameRender, Telemetry } = useShaderTelemetry({
position: 'top-right',
updateInterval: 100,
graphSamples: 60
});Low-level hook for WebGL context and shader program management.
Manages WebGL textures from uniform definitions.
Tracks normalized mouse position [0, 1] within an element.
Observes element resize with efficient ResizeObserver API.
const vertexShader = `
attribute vec2 a_position;
varying vec2 v_uv;
void main() {
v_uv = a_position * 0.5 + 0.5;
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
<ShaderCanvas
vertexShader={vertexShader}
fragmentShader={fragmentShader}
/><ShaderCanvas
fragmentShader={shader}
options={{
builtInUniforms: {
u_time: 'time',
u_resolution: 'resolution',
u_mouse: 'mouse'
}
}}
/>import { useMemo, useState } from 'react';
function VideoShader() {
const [video] = useState(() => {
const vid = document.createElement('video');
vid.src = '/path/to/video.mp4';
vid.loop = true;
vid.play();
return vid;
});
const uniforms = useMemo<Uniforms>(() => ({
u_video: {
type: 'texture',
value: video
}
}), [video]);
return <ShaderCanvas fragmentShader={shader} uniforms={uniforms} />;
}const canvasRef = useRef<HTMLCanvasElement>(null);
<ShaderCanvas
ref={canvasRef}
fragmentShader={shader}
/>const shader = `
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec3 color = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4));
gl_FragColor = vec4(color, 1.0);
}
`;const shader = `
precision mediump float;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float dist = distance(uv, u_mouse);
vec3 color = vec3(1.0 - smoothstep(0.0, 0.3, dist));
gl_FragColor = vec4(color, 1.0);
}
`;import { useMemo, useState, useEffect } from 'react';
function TextureMappingExample() {
const [image, setImage] = useState<HTMLImageElement | null>(null);
useEffect(() => {
const img = new Image();
img.src = '/texture.jpg';
img.onload = () => setImage(img);
}, []);
const shader = `
precision mediump float;
uniform vec2 u_resolution;
uniform sampler2D u_texture;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec4 texColor = texture2D(u_texture, uv);
gl_FragColor = texColor;
}
`;
const uniforms = useMemo<Uniforms>(() => {
if (!image) return {};
return {
u_texture: { type: 'texture', value: image }
};
}, [image]);
if (!image) return <div>Loading...</div>;
return <ShaderCanvas fragmentShader={shader} uniforms={uniforms} />;
}This library requires WebGL support. It works in all modern browsers:
- Chrome/Edge 9+
- Firefox 4+
- Safari 5.1+
- Opera 12+
- iOS Safari 8+
- Android Browser 4.4+
- Memoize uniforms object - Always wrap uniforms in
useMemo()to prevent unnecessary updates - Minimize uniform updates - Only include changing values in dependency array
- Use appropriate texture sizes - Large textures impact performance
- Optimize shader complexity - Complex calculations affect frame rate
- Monitor performance - Use
useShaderTelemetryto track metrics - Consider pixel ratio - Lower pixel ratio improves performance on high-DPI displays
MIT
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
- π Report bugs
- π‘ Request features
- π Documentation (WIP)