Introduction
Three.js est la bibliothèque JavaScript incontournable pour exploiter WebGL sans plonger dans ses complexités natives. En 2026, avec les avancées en hardware GPU et les navigateurs optimisés, elle permet de créer des expériences 3D immersives directement dans le navigateur, des visualisations de données aux jeux légers.
Ce tutoriel intermediate vous guide pas-à-pas pour bâtir une scène 3D interactive complète : scène basique, géométrie avec mesh, éclairage réaliste, animations fluides, contrôles orbit et interactions au clic via raycasting. Chaque étape est illustrée par un code HTML autonome et fonctionnel, utilisant le CDN officiel de Three.js pour une mise en œuvre immédiate.
Pourquoi c'est crucial ? Les sites e-commerce, portfolios et dashboards intègrent désormais la 3D pour booster l'engagement utilisateur de 40% (source : études Google). À la fin, vous disposerez d'une base bookmarkable pour scaler vers des projets complexes comme des modèles GLTF ou des shaders personnalisés. Prêt à rendre le web spatial ? (142 mots)
Prérequis
- Connaissances solides en JavaScript ES6+ et DOM manipulation.
- Navigateur moderne (Chrome 120+, Firefox 120+).
- Éditeur de code (VS Code recommandé).
- Pas d'installation serveur : tout via CDN et fichier HTML local.
Configuration de base : Renderer et boucle d'animation
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Basique</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.169.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.169.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.z = 5;
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>Ce code initialise une scène vide avec une caméra perspective, un renderer WebGL antialiased et une boucle d'animation via requestAnimationFrame. Le gestionnaire de resize maintient les proportions. Attention : utilisez toujours un importmap pour les modules ES6 en CDN, évitant les erreurs de résolution de modules.
Ajout d'un mesh : Premier objet 3D
Maintenant, introduisons un cube comme premier mesh. Un mesh combine une géométrie (forme) et un matériau (apparence). Pensez à la géométrie comme un squelette et au matériau comme la peau.
Ajout d'un cube avec matériau basique
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Cube</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.169.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.169.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>On crée un BoxGeometry et un MeshBasicMaterial (non affecté par la lumière). Le cube est ajouté à la scène et tourne légèrement dans animate(). Piège courant : oublier scene.add() rend l'objet invisible ; BasicMaterial est idéal pour tester sans lumières.
Éclairage réaliste
MeshBasicMaterial ignore les lumières. Passons à MeshStandardMaterial avec des lumières pour un rendu physique. Analogie : comme passer d'une ampoule nue à un spot directionnel.
Ajout de lumières et matériau standard
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Lumières</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.169.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.169.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true;
cube.position.y = 0.5;
scene.add(cube);
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
const planeGeometry = new THREE.PlaneGeometry(5, 5);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true;
scene.add(plane);
camera.position.set(0, 2, 5);
camera.lookAt(0, 0, 0);
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>AmbientLight pour éclairage global doux, DirectionalLight pour ombres réalistes (activez shadowMap). MeshStandardMaterial réagit aux lumières ; ajoutez un plan receveur d'ombres. Piège : sans shadowMap.enabled, pas d'ombres ; augmentez mapSize pour netteté sur grands écrans.
Contrôles interactifs avec OrbitControls
Pour manipuler la vue, intégrez OrbitControls : rotation, zoom, pan intuitifs.
Intégration d'OrbitControls
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js OrbitControls</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.169.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.169.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true;
cube.position.y = 0.5;
scene.add(cube);
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
const planeGeometry = new THREE.PlaneGeometry(5, 5);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true;
scene.add(plane);
camera.position.set(0, 2, 5);
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
controls.update();
});
</script>
</body>
</html>Importez OrbitControls et liez-le à camera/renderer. enableDamping simule l'inertie. Appelez controls.update() dans animate(). Piège : sans update() dans resize, les contrôles buguent ; dampingFactor >0.1 rend trop lent sur mobile.
Interactions avancées : Raycasting pour clics
Rendez la scène réactive : détectez les clics sur le cube pour changer sa couleur.
Raycasting pour sélection d'objets
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Raycasting</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; cursor: pointer; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.169.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.169.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true;
cube.position.y = 0.5;
scene.add(cube);
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
const planeGeometry = new THREE.PlaneGeometry(5, 5);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true;
scene.add(plane);
camera.position.set(0, 2, 5);
function onMouseClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects([cube]);
if (intersects.length > 0) {
material.color.setHex(Math.random() * 0xffffff);
}
}
window.addEventListener('click', onMouseClick);
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
controls.update();
});
</script>
</body>
</html>Raycaster projette un rayon depuis la souris vers la caméra ; intersectObjects cible le cube. Au clic, changez la couleur aléatoirement. Piège : normalisez mouse (-1 à +1) correctement ; passez un tableau d'objets pour scaler à plusieurs meshes.
Optimisations pour performances
Bonnes pratiques
- Activez renderer.toneMapping = THREE.ACESFilmicToneMapping pour un rendu HDR-like.
- Utilisez InstancedMesh pour 1000+ objets identiques (économie GPU).
- Disposez géométries/matériaux inutiles : geometry.dispose(), material.dispose().
- Limitez draw calls : fusionnez meshes quand possible.
- Testez FPS avec Stats.js ; ciblez 60 FPS sur mobile.
Erreurs courantes à éviter
- Oublier dispose() : fuites mémoire sur longues sessions → crash.
- Caméra statique sans controls : scène figée, UX nulle.
- Shadows sans bounds : artefacts visuels ; set shadow.cameraLeft/Right.
- Pas de near/far optimaux : clipping objets ou Z-fighting.
Pour aller plus loin
Approfondissez avec les loaders GLTF (modèles 3D pros), post-processing (bloom, FXAA) et shaders personnalisés via ShaderMaterial.
Ressources :
Découvrez nos formations Learni sur WebGL et 3D pour maîtriser la VR/AR avec Three.js.