Back to Components
Interactive GLSL Fire Spiral Animation
Component

Interactive GLSL Fire Spiral Animation

CodewithLord
October 4, 2025

The animation uses GLSL fragment shaders to create dynamic, flame-like spirals that react to mouse movement.

Description

The animation uses GLSL fragment shaders to create dynamic, flame-like spirals that react to mouse movement. The system relies purely on WebGL, GLSL, and JavaScript, without any HTML elements included in this file.


Vertex Shader

1attribute vec2 a_position; 2void main() { 3 gl_Position = vec4(a_position, 0.0, 1.0); 4}

Purpose:

  • Defines a fullscreen quad by passing raw vertex positions directly to clip space.
  • No transformations are applied; fragment shader does all the heavy lifting.

Fire Color Palette Function (GLSL)

1vec3 palette_fire(float t, float factor) { 2 vec3 a = vec3(0.5, 0.1, 0.0); 3 vec3 b = vec3(0.6, 0.3, 0.1); 4 vec3 c = vec3(1.0, 1.0, 0.0); 5 vec3 d = vec3(0.8, 0.7, 0.2); 6 7 a += 0.1 * sin(vec3(0.1, 0.2, 0.3) * factor); 8 b += 0.2 * cos(vec3(0.2, 0.3, 0.1) * factor); 9 10 return a + b * cos(6.28318 * (c * t + d)); 11}

Purpose:

  • Generates fiery tones using oscillating sine/cosine combinations.
  • Creates heat variation effects based on distance and time.

Fragment Shader Core Logic

1vec2 st = (gl_FragCoord.xy / u_resolution.xy) * 2.0 - 1.0; 2st.x *= u_resolution.x/u_resolution.y;
  • Normalizes pixel coordinates to –1 to 1 range.
  • Adjusts aspect ratio to prevent stretching.
1vec2 mouse_vec = st - mouse_st; 2float mouse_dist = length(mouse_vec); 3float mouse_push = smoothstep(0.7, 0.0, mouse_dist) * 0.5;
  • Computes mouse attraction force.
  • Creates a warping displacement around the mouse pointer.
1float twist = 0.5 * sin(R_global * 3.0 - u_time * 0.4); 2st *= mat2(cos(twist), sin(twist), -sin(twist), cos(twist));
  • Applies a time-based twisting field.
  • Produces swirling, spiral-like distortion.

Multi‑Layer Spiral Loop

1for (float i = 1.0; i < 6.0; i++) { 2 float num_arms = 4.0 + 3.0 * sin(u_time * 0.1 + i); 3 ... 4 vec3 pal = palette_fire(...); 5 color += pal * w; 6}

Purpose:

  • Runs multiple layers of spirals, each rotating at different speeds.
  • Adds depth, glow, and fire-like complexity.
  • Uses exponential and sinusoidal warping for dynamic shapes.

Final Output:

1gl_FragColor = vec4(color, 1.0);

JavaScript Logic

Shader Compilation

1createShader(gl, type, source)
  • Compiles GLSL vertex and fragment shaders.

Program Linking

1createProgram(gl, vertexShader, fragmentShader)
  • Links both shaders into a single GPU program.

Full Screen Quad Setup

1const positions = [-1, -1, 1, -1, -1, 1, 1, 1];
  • Creates square covering entire screen.

Uniform Updates

1gl.uniform2f(resolutionLocation, canvas.width, canvas.height); 2gl.uniform1f(timeLocation, time * 0.001); 3gl.uniform2f(mouseLocation, mouseX, mouseY);
  • Continuously feeds time, resolution, and mouse data to GPU.

Render Loop

1requestAnimationFrame(render);
  • Redraws animation every frame.
  • Produces smooth real-time animation at 60fps.

Canvas Resize System

  • Adjusts drawing buffer for high DPI displays.
  • Keeps shader output crisp on all screen sizes.

Summary

This WebGL fire spiral animation combines:

  • GLSL shaders for visual output
  • Mouse interaction for warp effects
  • Recursive spiral logic for complex fire patterns
  • High DPI adaptive rendering

The result is a high‑performance GPU-powered fire spiral that reacts fluidly to user input.


Full Code:

1<!DOCTYPE html> 2<html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <title>Interactive GLSL Shader Fire Spiral Animation in WebGL</title> 7 8 9 </head> 10 11 <body> 12 <style> 13body, html { 14 height: 100vh; 15 margin: 0; 16 overflow: hidden; 17 background-color: #000; 18} 19canvas { 20 display: block; 21} 22</style> 23<canvas id="glcanvas"></canvas> 24<script id="vertex-shader" type="x-shader/x-vertex"> 25attribute vec2 a_position; 26void main() { 27 gl_Position = vec4(a_position, 0.0, 1.0); 28} 29</script> 30<script id="fragment-shader" type="x-shader/x-fragment"> 31#ifdef GL_ES 32precision highp float; 33#endif 34uniform vec2 u_resolution; 35uniform vec2 u_mouse; 36uniform float u_time; 37vec3 palette_fire(float t, float factor) { 38 vec3 a = vec3(0.5, 0.1, 0.0); 39 vec3 b = vec3(0.6, 0.3, 0.1); 40 vec3 c = vec3(1.0, 1.0, 0.0); 41 vec3 d = vec3(0.8, 0.7, 0.2); 42 43 a += 0.1 * sin(vec3(0.1, 0.2, 0.3) * factor); 44 b += 0.2 * cos(vec3(0.2, 0.3, 0.1) * factor); 45 46 return a + b * cos(6.28318 * (c * t + d)); 47} 48void main() { 49 vec2 st = (gl_FragCoord.xy / u_resolution.xy) * 2.0 - 1.0; 50 st.x *= u_resolution.x/u_resolution.y; 51 vec3 color = vec3(0.0); 52 vec2 mouse_st = vec2(u_mouse.x, u_resolution.y - u_mouse.y) / u_resolution.xy; 53 mouse_st = (mouse_st * 2.0 - 1.0) * vec2(1.0, -1.0); 54 mouse_st.x *= u_resolution.x / u_resolution.y; 55 56 vec2 mouse_vec = st - mouse_st; 57 float mouse_dist = length(mouse_vec); 58 float mouse_push = smoothstep(0.7, 0.0, mouse_dist) * 0.5; 59 60 if (u_mouse.x > 0.0) { 61 st += normalize(mouse_vec) * mouse_push; 62 } 63 float R_global = length(st); 64 float angle_global = atan(st.y, st.x); 65 float twist = 0.5 * sin(R_global * 3.0 - u_time * 0.4); 66 st *= mat2(cos(twist), sin(twist), -sin(twist), cos(twist)); 67 for (float i = 1.0; i < 6.0; i++) { 68 vec2 st0 = st; 69 float sgn = 1.0 - 2.0 * mod(i, 2.0); 70 71 float t = u_time * 0.02 - float(i); 72 st0 *= mat2(cos(t), sin(t), -sin(t), cos(t)); 73 74 float R = length(st0); 75 float d = R * i; 76 float angle = atan(st0.y, st0.x); 77 float num_arms = 4.0 + 3.0 * sin(u_time * 0.1 + i); 78 float angle_warped = angle * num_arms; 79 float dist_warp_factor = 1.0 + 0.3 * sin(angle * 12.0 + u_time * 0.5 - i); 80 float d_warped = d * dist_warp_factor; 81 82 vec3 pal = palette_fire(-exp((length(d_warped) * -0.9)), abs(d_warped) * 0.4); 83 float radial = exp(-R); 84 radial *= smoothstep(1.2, 0.5, R); 85 pal *= radial; 86 float phase = -(d_warped + sgn * angle_warped) + u_time * 0.3; 87 88 float v = sin(phase); 89 v = max(abs(v), 0.01); 90 float w = pow(0.02 / v, 0.8); 91 color += pal * w; 92 } 93 gl_FragColor = vec4(color, 1.0); 94} 95</script> 96<script> 97const canvas = document.getElementById('glcanvas'); 98const gl = canvas.getContext('webgl'); 99if (!gl) { 100 console.error("WebGL not supported, falling back on experimental-webgl"); 101 gl = canvas.getContext("experimental-webgl"); 102} 103if (!gl) { 104 alert("Your browser does not support WebGL"); 105} 106function getShaderSource(id) { 107 return document.getElementById(id).textContent; 108} 109const vertexShaderSource = getShaderSource('vertex-shader'); 110const fragmentShaderSource = getShaderSource('fragment-shader'); 111function createShader(gl, type, source) { 112 const shader = gl.createShader(type); 113 gl.shaderSource(shader, source); 114 gl.compileShader(shader); 115 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 116 console.error('An error occurred compiling the shaders:', gl.getShaderInfoLog(shader)); 117 gl.deleteShader(shader); 118 return null; 119 } 120 return shader; 121} 122function createProgram(gl, vertexShader, fragmentShader) { 123 const program = gl.createProgram(); 124 gl.attachShader(program, vertexShader); 125 gl.attachShader(program, fragmentShader); 126 gl.linkProgram(program); 127 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 128 console.error('Unable to initialize the shader program:', gl.getProgramInfoLog(program)); 129 return null; 130 } 131 return program; 132} 133const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource); 134const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource); 135const program = createProgram(gl, vertexShader, fragmentShader); 136const positionBuffer = gl.createBuffer(); 137gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); 138const positions = [ 139 -1.0, -1.0, 140 1.0, -1.0, 141 -1.0, 1.0, 142 1.0, 1.0, 143]; 144gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); 145const positionLocation = gl.getAttribLocation(program, 'a_position'); 146gl.enableVertexAttribArray(positionLocation); 147gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); 148gl.useProgram(program); 149const resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); 150const timeLocation = gl.getUniformLocation(program, 'u_time'); 151const mouseLocation = gl.getUniformLocation(program, 'u_mouse'); 152let mouseX = -1.0; 153let mouseY = -1.0; 154 155let devicePixelRatio = Math.min(window.devicePixelRatio || 1, 2); 156window.addEventListener('mousemove', (e) => { 157 mouseX = e.clientX; 158 mouseY = e.clientY; 159}); 160function render(time) { 161 gl.uniform2f(resolutionLocation, canvas.width, canvas.height); 162 gl.uniform1f(timeLocation, time * 0.001); 163 gl.uniform2f(mouseLocation, mouseX * devicePixelRatio, mouseY * devicePixelRatio); 164 165 gl.clearColor(0, 0, 0, 1); 166 gl.clear(gl.COLOR_BUFFER_BIT); 167 168 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 169 170 requestAnimationFrame(render); 171} 172function resize() { 173 devicePixelRatio = Math.min(window.devicePixelRatio || 1, 2); 174 175 canvas.width = Math.floor(window.innerWidth * devicePixelRatio); 176 canvas.height = Math.floor(window.innerHeight * devicePixelRatio); 177 178 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 179 180 canvas.style.width = window.innerWidth + "px"; 181 canvas.style.height = window.innerHeight + "px"; 182} 183resize(); 184window.addEventListener('resize', resize); 185requestAnimationFrame(render); 186</script> 187 188 </body> 189 190</html>

Love this component?

Explore more components and build amazing UIs.

View All Components