Building a 3D Tech Stack Visualisation with Three.js
Introduction
Skill badges and icon grids are fine for a README, but for a portfolio website, you can do a lot better. When I was building my personal site at shavrka.studio, I wanted a tech stack section that actually felt alive — something that stopped visitors mid-scroll rather than blending into the background.
Three.js was the obvious tool for the job. It's a mature WebGL library with a massive ecosystem, excellent documentation, and enough abstraction that you're not writing raw shader code on day one. The result was an interactive 3D sphere of floating tech logos that users can rotate, hover, and interact with.
In this post I'll walk through how I built it — from scene setup to animation to React integration. It's less complex than it might sound, and the concepts transfer to dozens of other use cases.
Core Concepts
How Three.js Thinks
- Scene — the container for all 3D objects
- Camera — your viewpoint into the scene (typically
PerspectiveCamera) - Renderer — the engine that draws the scene to a
<canvas>using WebGL - Mesh — an object made of geometry (shape) + material (appearance)
- Light — ambient or directional lighting that affects how materials look
The rendering loop looks like this:
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();That's the heartbeat of every Three.js project. Everything else is building objects, positioning them, and updating them on each frame.
Spherical Distribution of Points
To distribute objects evenly across a sphere's surface, I used the Fibonacci sphere algorithm — a technique that avoids clustering at the poles (which you get from naïve latitude/longitude loops):
function fibonacciSphere(n, radius) {
const points = [];
const goldenAngle = Math.PI * (3 - Math.sqrt(5)); // ~2.399 radians
for (let i = 0; i < n; i++) {
const y = 1 - (i / (n - 1)) * 2; // y from 1 to -1
const r = Math.sqrt(1 - y * y);
const theta = goldenAngle * i;
points.push(
new THREE.Vector3(
Math.cos(theta) * r * radius,
y * radius,
Math.sin(theta) * r * radius
)
);
}
return points;
}This gives you perfectly even distribution — no dead zones, no bunching. Pass in the number of icons you have and the sphere radius, and you get exactly that many positions.
Practical Examples
Scene Setup
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75, // field of view
container.clientWidth / container.clientHeight, // aspect ratio
0.1, // near clipping plane