Back to Components
Interactive Sphere of Squares Animation Using HTML Canvas & JavaScript
Component

Interactive Sphere of Squares Animation Using HTML Canvas & JavaScript

CodewithLord
December 15, 2025

This project demonstrates a mesmerizing 3D-like sphere made of rotating squares using HTML Canvas and JavaScript. It recreates classic AS3-style orthographic 3D logic with custom matrix transformations, mouse interaction, and glow effects.

🧠 Description

This project showcases an interactive sphere of squares animation rendered on an HTML <canvas> element using pure JavaScript.
The animation recreates classic ActionScript 3 (AS3) 3D logic by implementing a custom 4×4 matrix system for rotations, scaling, and translations.

Thousands of small squares are positioned in 3D space to form a spherical structure.
Mouse movement smoothly rotates the sphere, while an automatic rotation keeps the animation alive when the user is idle.

A blur + screen blend effect adds a soft glowing aesthetic, giving the animation a modern and visually appealing look — perfect for creative coding showcases, portfolios, or experimental visuals.


💻 HTML Code


1<!DOCTYPE html> 2<html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <title>sphere of squares</title> 7 <link rel="stylesheet" href="./style.css"> 8 9 </head> 10 11 <body> 12 <canvas id="stage" width="1200" height="1200"></canvas> 13 <script src="./script.js"></script> 14 15 </body> 16 17</html>

HTML Explanation


The HTML structure is minimal and focused entirely on rendering graphics.

A single <canvas> element acts as the rendering surface.

The canvas is sized to 1200×1200 pixels to provide high-resolution visuals.

External CSS handles layout and scaling.

JavaScript drives all animation, interaction, and rendering logic.


🎨 CSS Code


1body { 2 margin: 0; 3 padding: 0; 4 background-color: black; 5 overflow: hidden; 6} 7 8canvas { 9 position: relative; 10 display: block; 11 margin: 0 auto; 12 scale: 0.5 0.5; 13 left: 15px; 14 width: 200%; 15 transform-origin: 0 0; 16}

CSS Explanati


The CSS ensures the canvas remains centered and visually optimized.

The background is set to pure black for strong contrast.

Overflow is hidden to prevent scrollbars during animation.

The canvas is scaled down while maintaining a large internal resolution, improving visual sharpness.

Transform origin is adjusted to allow precise scaling control.


1/* looks like Gemini 3 is now able to port as3 stuff that it failed on a few months back :tada: 2 3 - is the code good? /Not really :P 4 - does it work? /Yes 5 - can any AI use DOMMatrix instead of a custom matrix thing? /Nope - LLMs still struggle with DOMMatrix sometimes 6 7 */ 8 9/** 10 * RECREATION OF AS3 LOGIC (Orthographic Update) 11 */ 12 13// --- Setup --- 14const canvas = document.getElementById("stage"); 15const ctx = canvas.getContext("2d"); 16 17// Offscreen buffer for the blur effect 18const bufferCanvas = document.createElement("canvas"); 19bufferCanvas.width = 1200; 20bufferCanvas.height = 1200; 21const bufferCtx = bufferCanvas.getContext("2d"); 22 23// Variables from AS3 24const squareNum = 1000; 25const stageWidth = 1200; 26const stageHeight = 1200; 27const hw = stageWidth / 2; 28const hh = stageHeight / 2; 29 30// verts defines a single square 31const baseVerts = [-40, 0, 0, 40, 0, 0, 40, 0, 80, -40, 0, 80, -40, 0, 0]; 32 33// Storage for vertices 34let newVerts = []; 35const radius = 400; 36 37// Mouse easing 38let dx = 0; 39let dy = 0; 40let mouseX = 0; 41let mouseY = 0; 42 43canvas.addEventListener("mousemove", (e) => { 44 const rect = canvas.getBoundingClientRect(); 45 mouseX = e.clientX - rect.left; 46 mouseY = e.clientY - rect.top; 47}); 48 49// --- Matrix Helper Class --- 50class Matrix3D { 51 constructor() { 52 this.identity(); 53 } 54 55 identity() { 56 this.d = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 57 } 58 59 appendScale(x, y, z) { 60 this.append([x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1]); 61 } 62 63 appendTranslation(x, y, z) { 64 this.append([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1]); 65 } 66 67 appendRotation(deg, axis) { 68 const rad = (deg * Math.PI) / 180; 69 const c = Math.cos(rad); 70 const s = Math.sin(rad); 71 let m = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 72 73 if (axis === "X") { 74 m[5] = c; 75 m[6] = s; 76 m[9] = -s; 77 m[10] = c; 78 } else if (axis === "Y") { 79 m[0] = c; 80 m[2] = -s; 81 m[8] = s; 82 m[10] = c; 83 } else if (axis === "Z") { 84 m[0] = c; 85 m[1] = s; 86 m[4] = -s; 87 m[5] = c; 88 } 89 this.append(m); 90 } 91 92 append(b) { 93 const a = this.d; 94 const out = []; 95 for (let i = 0; i < 4; i++) { 96 for (let j = 0; j < 4; j++) { 97 let sum = 0; 98 for (let k = 0; k < 4; k++) { 99 sum += a[i * 4 + k] * b[k * 4 + j]; 100 } 101 out[i * 4 + j] = sum; 102 } 103 } 104 this.d = out; 105 } 106 107 // Standard 3D transformation 108 transformVectors(inVerts, outVerts) { 109 const m = this.d; 110 for (let i = 0; i < inVerts.length; i += 3) { 111 const x = inVerts[i]; 112 const y = inVerts[i + 1]; 113 const z = inVerts[i + 2]; 114 115 outVerts.push( 116 x * m[0] + y * m[4] + z * m[8] + m[12], 117 x * m[1] + y * m[5] + z * m[9] + m[13], 118 x * m[2] + y * m[6] + z * m[10] + m[14] 119 ); 120 } 121 } 122} 123 124// --- Initialization --- 125const m = new Matrix3D(); 126 127// Generate the sphere of squares 128for (let i = 0; i < squareNum; i++) { 129 m.identity(); 130 const s = Math.random() * 0.5 + 0.5; 131 m.appendScale(s, s, s); 132 m.appendRotation(90, "X"); 133 m.appendTranslation(0, 0, radius); 134 m.appendRotation(Math.random() * 360, "X"); 135 m.appendRotation(Math.random() * 360, "Y"); 136 m.appendRotation(Math.random() * 360, "Z"); 137 138 m.transformVectors(baseVerts, newVerts); 139} 140let down; 141 142onpointerdown = (e) => { 143 down = true; 144}; 145onpointerup = (e) => { 146 down = false; 147}; 148// --- Main Loop --- 149let ox = (oy = 0); 150 151function onLoop() { 152 dx += (mouseX + ox - dx) / 14; 153 dy += (mouseY + oy - dy) / 14; 154 155 if (!down) { 156 ox += 0.3; 157 oy += 0.3; 158 } 159 160 // View Matrix 161 m.identity(); 162 m.appendRotation(dx, "Z"); 163 m.appendRotation(dy, "X"); 164 m.appendTranslation(hw, hh, 0); 165 166 const mat = m.d; 167 const pVerts = []; // projected vertices 168 169 // Project Vectors 170 // The original AS3 code used Utils3D.projectVectors with a standard transformation matrix. 171 // Without specific Perspective adjustments, this results in an Orthographic projection. 172 // Objects are rotated and moved to screen center, but z-depth does not affect scale. 173 for (let i = 0; i < newVerts.length; i += 3) { 174 const x = newVerts[i]; 175 const y = newVerts[i + 1]; 176 const z = newVerts[i + 2]; 177 178 // Apply Matrix Transform (Rotate + Translate to center) 179 const vx = x * mat[0] + y * mat[4] + z * mat[8] + mat[12]; 180 const vy = x * mat[1] + y * mat[5] + z * mat[9] + mat[13]; 181 // Z is calculated but ignored for scaling in orthographic projection 182 183 pVerts.push(vx, vy); 184 } 185 186 // --- Rendering --- 187 bufferCtx.clearRect(0, 0, stageWidth, stageHeight); 188 189 // 1. Draw Background Circle (White) 190 // Radius + 10 (210px) vs Sphere Radius (200px) creates the border 191 bufferCtx.fillStyle = "#FFFFFF"; 192 bufferCtx.beginPath(); 193 bufferCtx.arc(hw, hh, radius + 20, 0, Math.PI * 2); 194 bufferCtx.fill(); 195 196 // 2. Draw Squares (Black) 197 bufferCtx.fillStyle = "#000000"; 198 bufferCtx.beginPath(); 199 200 for (let i = 0; i < pVerts.length; i += 10) { 201 bufferCtx.moveTo(pVerts[i], pVerts[i + 1]); 202 bufferCtx.lineTo(pVerts[i + 2], pVerts[i + 3]); 203 bufferCtx.lineTo(pVerts[i + 4], pVerts[i + 5]); 204 bufferCtx.lineTo(pVerts[i + 6], pVerts[i + 7]); 205 bufferCtx.lineTo(pVerts[i + 8], pVerts[i + 9]); 206 } 207 bufferCtx.fill(); 208 209 // --- Compositing --- 210 211 // Fill background black 212 ctx.globalCompositeOperation = "source-over"; 213 ctx.filter = "none"; 214 ctx.fillStyle = "#000000"; 215 ctx.fillRect(0, 0, stageWidth, stageHeight); 216 217 // Draw sharp version 218 ctx.drawImage(bufferCanvas, 0, 0); 219 220 // Draw blurred version (Screen Blend) 221 ctx.globalCompositeOperation = "screen"; 222 ctx.filter = "blur(20px)"; 223 ctx.drawImage(bufferCanvas, 0, 0); 224 225 // Reset 226 ctx.filter = "none"; 227 ctx.globalCompositeOperation = "source-over"; 228 229 requestAnimationFrame(onLoop); 230} 231 232requestAnimationFrame(onLoop); 233

✨ Key Features

Custom 3D matrix math (no libraries)

Orthographic 3D projection

Mouse-driven rotation with easing

Glow & blur compositing effect

AS3-style logic recreated in JavaScript

High-performance canvas rendering

Love this component?

Explore more components and build amazing UIs.

View All Components