Project Overview
3D Zig-Zag Matrix — a small interactive visual that renders a zig‑zag numbered matrix as a rotating 3D wave of colored points on an HTML canvas. It started as a compact demo combining a classic zig‑zag matrix generator with a simple perspective projection and animation. I finished it by polishing the visuals, fixing projection/rotation math, adding responsive canvas sizing, and improving the demo copy for the GitHub/DEV submission.
Polished DEV Submission (ready to paste)
What I Built
I built a small interactive demo called 3D Zig‑Zag Matrix that visualizes a zig‑zag traversal of an
𝑛
×
𝑛
matrix as a rotating 3D wave of colored points. The project combines a compact algorithmic generator with a lightweight 3D projection and animation loop so you can see the zig‑zag order come alive.
Demo
Live demo: (paste your hosted URL here)
Screenshots / GIF: Add a short GIF or screenshot showing the rotating wave.
How to run locally: Clone the repo, open index.html in a browser, or serve with a static server.
The Comeback Story
The project began as a small codepen-style prototype with working logic but rough visuals and a few bugs in the projection and resizing behavior. To finish it up I:
Fixed the 3D projection and rotation math so the grid rotates smoothly.
Made the canvas responsive to window resizes and device pixel ratio.
Added a wave effect tied to the zig‑zag index for a more organic motion.
Cleaned up the code and added comments so others can extend it.
My Experience with GitHub Copilot
GitHub Copilot helped speed up the iteration loop: it suggested the initial projection formula, offered small refactors for the animation loop, and proposed color cycling logic. I reviewed and adapted the suggestions to match the visual style I wanted.
How to Run Locally
Save the HTML below as index.html and the JavaScript as zigzag.js in the same folder.
Open index.html in a modern browser (Chrome, Edge, Firefox).
Optionally serve with a static server (e.g., npx http-server) for a stable local URL.
Improved Code
index.html
html
<!doctype html>
<br> :root { background: #000; }<br> html,body { height:100%; margin:0; }<br> body {<br> display:flex;<br> align-items:center;<br> justify-content:center;<br> background:#000;<br> color:#fff;<br> font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;<br> }<br> canvas { display:block; width:100%; height:100vh; }<br> .ui {<br> position:fixed;<br> left:12px;<br> top:12px;<br> z-index:10;<br> background:rgba(0,0,0,0.4);<br> padding:8px 10px;<br> border-radius:8px;<br> backdrop-filter:blur(4px);<br> color:#fff;<br> font-size:13px;<br> }<br> .ui input { width:48px; }<br>
Size:
Cell:
zigzag.js
javascript
// Zig-zag matrix generator
function createZigZagMatrix(n) {
const matrix = Array.from({ length: n }, () => Array(n).fill(0));
let num = 0;
for (let d = 0; d < 2 * n - 1; d++) {
if (d % 2 === 0) {
for (let i = Math.min(d, n - 1); i >= Math.max(0, d - n + 1); i--) {
matrix[i][d - i] = num++;
}
} else {
for (let i = Math.max(0, d - n + 1); i <= Math.min(d, n - 1); i++) {
matrix[i][d - i] = num++;
}
}
}
return matrix;
}
// Canvas setup
const canvas = document.getElementById('zigzagCanvas');
const ctx = canvas.getContext('2d', { alpha: false });
function resizeCanvas() {
const dpr = Math.max(1, window.devicePixelRatio || 1);
canvas.width = Math.floor(window.innerWidth * dpr);
canvas.height = Math.floor(window.innerHeight * dpr);
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// UI controls
const sizeInput = document.getElementById('sizeInput');
const cellInput = document.getElementById('cellInput');
let size = Math.max(2, Math.min(40, parseInt(sizeInput.value, 10) || 8));
let cellSize = Math.max(8, Math.min(80, parseInt(cellInput.value, 10) || 28));
sizeInput.addEventListener('change', () => {
size = Math.max(2, Math.min(40, parseInt(sizeInput.value, 10) || 8));
resetMatrix();
});
cellInput.addEventListener('change', () => {
cellSize = Math.max(8, Math.min(80, parseInt(cellInput.value, 10) || 28));
});
// Colors
const colors = [
'#FF6B6B', '#FFD93D', '#6BCB77', '#4D96FF', '#9B5DE5',
'#00BBF9', '#FF7AB6', '#F6AE2D', '#2EC4B6', '#FF6F91'
];
// State
let zigZagMatrix = createZigZagMatrix(size);
let angleX = 0;
let angleY = 0;
let time = 0;
function resetMatrix() {
zigZagMatrix = createZigZagMatrix(size);
}
// Simple 3D rotate and perspective projection
function project3D(x, y, z, cameraZ = 800, fov = 800) {
const zRel = cameraZ - z;
const scale = fov / zRel;
return {
x: x * scale,
y: y * scale,
scale
};
}
function drawMatrix() {
// Clear with slight fade for motion trails
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const cx = canvas.width / (2 * (window.devicePixelRatio || 1));
const cy = canvas.height / (2 * (window.devicePixelRatio || 1));
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
const value = zigZagMatrix[i][j];
// center grid around origin
const x0 = (j - (size - 1) / 2) * cellSize;
const y0 = (i - (size - 1) / 2) * cellSize;
// wave along value index and time
const wave = Math.sin(value * 0.25 + time * 0.02) * (cellSize * 0.9);
let x = x0;
let y = y0;
let z = wave;
// rotate around Y then X
const cosY = Math.cos(angleY), sinY = Math.sin(angleY);
const cosX = Math.cos(angleX), sinX = Math.sin(angleX);
// rotate Y
let rx = x * cosY - z * sinY;
let rz = x * sinY + z * cosY;
// rotate X
let ry = y * cosX - rz * sinX;
rz = y * sinX + rz * cosX;
const p = project3D(rx, ry, rz + 400, 1000, 1000);
const screenX = cx + p.x;
const screenY = cy + p.y;
// size based on depth
const radius = Math.max(2, 8 * p.scale);
ctx.beginPath();
ctx.fillStyle = colors[value % colors.length];
ctx.globalAlpha = 0.95;
ctx.arc(screenX, screenY, radius, 0, Math.PI * 2);
ctx.fill();
// subtle highlight
ctx.beginPath();
ctx.globalAlpha = 0.25;
ctx.fillStyle = '#fff';
ctx.arc(screenX - radius * 0.35, screenY - radius * 0.35, Math.max(0.6, radius * 0.35), 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
}
}
// animate
angleX += 0.007;
angleY += 0.01;
time += 1;
requestAnimationFrame(drawMatrix);
}
drawMatrix();
Notes and Suggestions
Hosting: Use GitHub Pages for a quick demo URL. Add the demo link to your DEV post.
Accessibility: Add keyboard controls to pause/step the animation and ARIA labels for the UI.
Extensions: Try rendering lines between points in zig‑zag order, or add a depth-sorted glow for a neon look.
Performance: For very large sizes, consider OffscreenCanvas or WebGL for faster rendering.
Top comments (0)