simple utility to render a .glsl fragment shader
10/4/2026
ts
shaders
if you’ve ever wanted to render a .glsl fragment shader in browser without heavyweight graphics libraries, i got you. just use setupWebGL(canvasElement, shader). position and size your canvas using css. to cleanup/destroy call the function returned.
import { setupWebGL } from "./webgl";
const shader = `#version 300 es
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
out vec4 fragColor;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec2 centered = (uv - 0.5) * 2.0;
float angle = atan(centered.y, centered.x) + u_time;
float normalized = fract((angle + PI) / (2.0 * PI));
fragColor = vec4(pow(normalized, -1.0) * vec3(uv, 1.0), 1.0);
}`;
setupWebGL(document.querySelector<HTMLCanvasElement>(".gl")!, shader);const quadVertexShaderGlsl = `#version 300 es
in vec4 position;
void main() {
gl_Position = position;
}`;
const quadVertices = new Float32Array(
[
[-1, 3],
[-1, -1],
[3, -1],
].flat(),
);
export function setupWebGL(
canvasElement: HTMLCanvasElement,
quadFragmentShaderGlsl: string,
): (() => void) | void {
const gl = canvasElement.getContext("webgl2");
if (!gl) return;
let dpr = window.devicePixelRatio;
let width = Math.floor(canvasElement.clientWidth * dpr);
let height = Math.floor(canvasElement.clientHeight * dpr);
const quadVertexShader = createShader(
gl,
gl.VERTEX_SHADER,
quadVertexShaderGlsl,
);
const quadFragmentShader = createShader(
gl,
gl.FRAGMENT_SHADER,
quadFragmentShaderGlsl,
);
const bgProgram = createProgram(gl, quadVertexShader, quadFragmentShader);
gl.deleteShader(quadVertexShader);
gl.deleteShader(quadFragmentShader);
const bgLocations = {
position: gl.getAttribLocation(bgProgram, "position"),
time: gl.getUniformLocation(bgProgram, "u_time"),
resolution: gl.getUniformLocation(bgProgram, "u_resolution"),
};
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, quadVertices, gl.STATIC_DRAW);
const quadVao = gl.createVertexArray();
gl.bindVertexArray(quadVao);
gl.enableVertexAttribArray(bgLocations.position);
gl.vertexAttribPointer(bgLocations.position, 2, gl.FLOAT, false, 0, 0);
let raf: number;
let startedAt = performance.now();
let resized = true;
const canvasResizeObserver = new ResizeObserver(() => (resized = true));
canvasResizeObserver.observe(canvasElement);
const frame = (time: number) => {
if (resized) {
dpr = window.devicePixelRatio;
width = Math.floor(canvasElement.clientWidth * dpr);
height = Math.floor(canvasElement.clientHeight * dpr);
canvasElement.width = width;
canvasElement.height = height;
gl.viewport(0, 0, width, height);
resized = false;
}
gl.useProgram(bgProgram);
gl.bindVertexArray(quadVao);
gl.uniform1f(bgLocations.time, (time - startedAt) / 1000);
gl.uniform2f(bgLocations.resolution, width, height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, Math.floor(quadVertices.length / 2));
raf = requestAnimationFrame(frame);
};
raf = requestAnimationFrame(frame);
return () => {
cancelAnimationFrame(raf);
canvasResizeObserver.disconnect();
const loseContextExt = gl.getExtension("WEBGL_lose_context");
if (loseContextExt) loseContextExt.loseContext();
};
}
function createShader(
gl: WebGL2RenderingContext,
type:
| WebGLRenderingContextBase["VERTEX_SHADER"]
| WebGLRenderingContextBase["FRAGMENT_SHADER"],
source: string,
): WebGLShader {
const shader = gl.createShader(type);
if (!shader) throw new Error("gl.createShader");
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return shader;
const log = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error(log ?? "?");
}
function createProgram(
gl: WebGL2RenderingContext,
vertexShader: WebGLShader,
fragmentShader: WebGLShader,
): WebGLProgram {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (gl.getProgramParameter(program, gl.LINK_STATUS)) return program;
const log = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(log ?? "?");
}credits:
have a question/comment? want to say hi?