aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Frontend & UI

Three.js Animation: Build a Live 3D Scene

When it comes to real-time 3D graphics in the browser, Three.js is the first library that comes to mind; in this article we will build Three.js animation logic from scratch to create a live scene that spins, catches light, and responds to user interaction. The goal is not just to rotate a cube, but to understand how the render loop works, why the time between frames matters, and how to keep the scene performant. By the end you will have a starter template that runs in a single HTML file and never drops frames.

The three building blocks of a Three.js scene

Every Three.js project revolves around three objects: the Scene, the Camera, and the Renderer. The scene is the container that holds everything visible. The camera defines the angle from which we look at that scene. The renderer uses WebGL to draw the result onto a <canvas>.

  • Scene: the root of the graph tree containing objects, lights, and cameras.
  • PerspectiveCamera: a camera that, like the human eye, makes distant objects appear smaller and gives a sense of depth.
  • WebGLRenderer: runs on the GPU and redraws the scene on every frame.

Importing the library as a module is the cleanest approach. Modern browsers can pull an ES module straight from a CDN using an import map.

<script type="importmap">
{
  "imports": {
    "three": "https://unpkg.com/three@0.160.0/build/three.module.js"
  }
}
</script>

<script type="module">
import * as THREE from 'three';
</script>

Setting up the first scene

Now let's create the scene, the camera, and the renderer. The camera parameters are, in order: field of view (fov, in degrees), aspect ratio, near clipping plane, and far clipping plane. To add the renderer to the page, we place its domElement property into the DOM.

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0b1020);

const camera = new THREE.PerspectiveCamera(
  60, window.innerWidth / window.innerHeight, 0.1, 100
);
camera.position.z = 4;

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);

Capping setPixelRatio at 2 is important: on high-density displays, limiting the pixel ratio keeps the resolution reasonable, eases the load on the GPU, and reduces heat build-up on mobile.

Object, material, and light

In Three.js a visible object (mesh) is the combination of a geometry (shape) and a material (surface appearance). MeshStandardMaterial is a physically based material that needs light to look realistic; that is why the screen stays pitch black until you add at least one light to the scene.

const geometry = new THREE.IcosahedronGeometry(1, 0);
const material = new THREE.MeshStandardMaterial({
  color: 0x4f9dde,
  roughness: 0.3,
  metalness: 0.6,
  flatShading: true,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

const keyLight = new THREE.DirectionalLight(0xffffff, 2);
keyLight.position.set(3, 4, 5);
scene.add(keyLight);
scene.add(new THREE.AmbientLight(0x404060, 1.5));

Here we use a directional light (sun-like, with a clear direction and hard shadows) together with an ambient light (equal from all directions, a fill that softens shadows). This pair gives a simple but balanced lighting setup.

The render loop and time-based animation

The heart of animation is the render loop. requestAnimationFrame tells the browser, "call me when you are ready for the next paint"; this usually happens 60 times a second, in sync with the screen's refresh rate. The naive approach adds a fixed value every frame, but that is wrong: it spins slowly on a 60 Hz screen and quickly on a 144 Hz one. The correct way is to multiply by the elapsed time (delta).

const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);

  const delta = clock.getDelta(); // elapsed time in seconds
  mesh.rotation.y += delta * 0.8;  // 0.8 radians per second
  mesh.rotation.x += delta * 0.3;

  renderer.render(scene, camera);
}
animate();

THREE.Clock gives you the real time elapsed between frames. Multiplying the rotation speed by this value makes the animation run at the same speed on every device, independent of the screen's refresh rate. This is one of the most visible differences between professional and amateur work.

Adapting to window size and interaction

So the image doesn't distort when the user resizes the window, we must update the camera and the renderer. When the camera's aspect ratio changes, a call to updateProjectionMatrix is mandatory.

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

For interaction, the fastest route is the official OrbitControls add-on, which lets you rotate and zoom the scene with the mouse. Just add it to the import map and put controls.update() in the loop. If you want a lighter feel, you can tie the mouse position to a subtle camera offset (a parallax effect).

Performance and cleanup tips

  • Pause when the tab is in the background: requestAnimationFrame already slows when the page is hidden, but for heavy scenes, stopping the loop entirely with a document.hidden check saves the battery.
  • Reuse geometry and material: if you need hundreds of copies of the same shape, InstancedMesh draws them all in a single draw call.
  • Watch for memory leaks: when removing an object permanently, call geometry.dispose() and material.dispose(); GPU memory is not freed automatically.
  • Use shadows sparingly: real-time shadow maps are expensive; keep them off when you don't need them.

Frequently Asked Questions

Do I need to know WebGL to learn Three.js?

No. Three.js abstracts away WebGL's complex shader and buffer details; to get started, JavaScript and basic 3D concepts (coordinates, vectors, cameras) are enough. WebGL knowledge becomes an advantage later when you want to write custom shaders, but it is not required.

Why does my animation spin so fast on some computers?

Most likely you are incrementing the rotation by a fixed number. On high-refresh-rate (120/144 Hz) screens, the loop runs more often and the motion speeds up. The fix is to multiply the motion by clock.getDelta(), that is, to use time-based animation.

Does Three.js work well on mobile devices?

Yes, but you need to be careful. Cap setPixelRatio, keep the polygon count low, avoid heavy post-processing effects, and limit shadows. With these measures you can run smooth 3D scenes even on mid-range phones.

Dreaming of a live 3D experience in the browser? From product configurators to interactive backgrounds, I design Three.js-based scenes and bring them to life with performance in mind. To talk about your project, get in touch with me.

Bu kategorideki tüm yazılar →

Devamı için