
In the world of web development, the combination of HTML, CSS, and JavaScript can create stunning visual experiences that go far beyond traditional web pages. The Symmetric Light animation demonstrates this beautifully, showcasing how WebGL can transform a blank canvas into a hypnotic, pulsating light show. This article explores the technical and artistic elements behind this captivating creation.
At the heart of this animation lies the HTML <canvas> element, a powerful tool that allows for dynamic, scriptable rendering of 2D and 3D graphics. What makes this implementation particularly interesting is its use of WebGL, a JavaScript API for rendering high-performance interactive graphics within compatible web browsers.
html
<canvas></canvas>This simple tag becomes a portal to an animated universe when combined with the right JavaScript code.
The code begins by establishing a full-screen canvas with a black background:
javascript
canvas.width = window.innerWidth;canvas.height = window.innerHeight;gl.viewport(0,0, canvas.width, canvas.height);This ensures the animation fills the entire viewport, creating an immersive experience.
The real wizardry happens in the shaders—special programs that run on the GPU. The code includes two shaders:
The fragment shader is particularly fascinating:
glsl
voidmain(){vec3 c;float time = iTime;float l =length(UV);vec2 n = UV / l;for(int i=0; i<3; i++){ time +=0.07;vec2 uv = UV +0.5; uv += n *(sin(time)+1.0)*abs(sin(l *9.0- time*2.0)); c[i]=0.01/length(mod(uv,1.0)-0.5);} gl_FragColor =vec4(c/l,1.0);}This code creates the hypnotic symmetric patterns by manipulating mathematical functions and time-based animations.
The rendering loop drives the animation:
javascript
functionrender(time){ time *=0.001;// Convert to seconds gl.uniform1f(iTimeLocation, time); gl.drawArrays(gl.TRIANGLE_STRIP,0,4);requestAnimationFrame(render);}By continuously updating the time uniform and redrawing the scene, the animation comes to life.
The Symmetric Light animation creates a visual experience that resembles:
The use of time-varying trigonometric functions (sin) creates organic, flowing motions that feel both mathematical and natural simultaneously.
WebGL leverages the GPU for rendering, making it highly efficient for complex animations. This implementation uses a simple geometry (a quad) but complex fragment shader calculations, offloading the heavy work to the graphics card.
The animation automatically adjusts to the viewport size, ensuring a consistent experience across devices.
While WebGL is widely supported in modern browsers, developers should consider fallbacks for older browsers or devices that don't support WebGL.
This type of animation could be adapted for:
The Symmetric Light WebGL animation demonstrates how a few lines of code can create captivating visual experiences. It blends mathematical precision with artistic expression, showing the power of modern web technologies to create immersive digital art.
By understanding the techniques behind this animation, developers can create their own unique visual experiences, pushing the boundaries of what's possible on the web. The combination of shader programming, time-based animations, and creative mathematics opens up a world of possibilities for web-based visual art.
As web technologies continue to evolve, we can expect to see even more impressive and performant graphics directly in the browser, making the web an increasingly powerful platform for digital artistry.





the entire html code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Symmetric Light</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #000;
}
canvas {
display: block;
/* width: 100vw;
height: 100vh; */
}
</style>
</head>
<body>
<canvas></canvas>
<script>
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
// Convert from [-1,1] to [-.5, .5]
const vsSource = `
attribute vec2 aPosition ;
varying vec2 UV ;
void main() {
UV = aPosition / 2.0;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
const fsSource = `
precision highp float ;
varying vec2 UV ;
uniform float iTime ;
void main() {
vec3 c;
float time = iTime;
float l = length(UV);
vec2 n = UV / l;
for(int i=0; i<3; i++) {
time += 0.07;
vec2 uv = UV + 0.5;
uv += n * (sin(time) + 1.0) * abs(sin(l * 9.0 - time*2.0));
c[i] = 0.01 / length(mod(uv, 1.0) - 0.5);
}
gl_FragColor = vec4(c/l, 1.0);
}
`;
// Compile shader
function compileShader(source, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
const vertexShader = compileShader(vsSource, gl.VERTEX_SHADER);
const fragmentShader = compileShader(fsSource, gl.FRAGMENT_SHADER);
// Create shader program
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
// Set up vertex buffers for a full-screen quad
const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "aPosition");
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// Get uniform locations
const iTimeLocation = gl.getUniformLocation(shaderProgram, "iTime");
// Animation loop
function render(time) {
time *= 0.001; // Convert to seconds
gl.uniform1f(iTimeLocation, time);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
</script>
</body>
</html>