Introduction
Three.js est la bibliothèque JavaScript incontournable pour manipuler WebGL sans plonger dans ses complexités natives. En 2026, elle reste le choix n°1 pour les développeurs web souhaitant intégrer des rendus 3D interactifs, des visualisations de données immersives ou des expériences AR/VR légères. Ce tutoriel intermediate vous guide pas à pas pour créer une scène 3D animée : d'une configuration basique à une sphère texturée en rotation avec contrôles orbitaux et ombres dynamiques.
Pourquoi c'est crucial ? Les sites e-commerce, portfolios et dashboards exploitent de plus en plus la 3D pour booster l'engagement (jusqu'à +40% de temps passé selon des études A/B). Vous apprendrez à optimiser les performances (60 FPS stables), gérer les événements souris et exporter vers des frameworks comme React. À la fin, vous aurez un exemple copier-collable prêt pour production, évitant les pièges courants comme les fuites mémoire ou les artefacts de rendu. Prêt à transformer vos pages HTML en mondes 3D ? (128 mots)
Prérequis
- Connaissances de base en HTML, CSS et JavaScript (ES6+).
- Navigateur moderne (Chrome 120+, Firefox 130+).
- Éditeur de code (VS Code recommandé).
- Pas d'installation : on utilise le CDN Three.js r169 (version stable 2026).
Configuration de base de la scène
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Scène de Base</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 crée une scène vide avec caméra perspective, renderer WebGL et boucle d'animation à 60 FPS. L'importmap ES modules simplifie l'utilisation du CDN sans bundler. Le resize handler évite les distorsions sur redimensionnement ; testez-le en ouvrant le fichier dans un navigateur.
Comprendre la boucle de rendu
La boucle requestAnimationFrame synchronise le rendu avec l'écran (vs setInterval qui gaspille CPU). Imaginez-la comme un moteur de voiture : elle tourne en continu pour fluidité. La scène est le conteneur, la caméra l'œil, le renderer le projecteur. Prochaine étape : ajouter un mesh pour visualiser.
Ajouter 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" } }
</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 ajoute un cube via BoxGeometry et MeshBasicMaterial (non affecté par la lumière). La rotation dans animate() crée un effet tournoiement fluide. Piège : sans scene.add(), le mesh est invisible ; BasicMaterial est idéal pour debug car rapide.
Matériaux et géométries
Les géométries définissent la forme (comme un moule), les matériaux la surface (couleur, réflexion). MeshBasicMaterial ignore les lumières pour simplicité. Passons aux lumières réalistes avec MeshStandardMaterial pour des rendus PBR (Physically Based Rendering).
Intégrer 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" } }
</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: 0xff6347 });
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, 0.5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
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>MeshStandardMaterial réagit aux lumières pour un rendu réaliste. AmbientLight illumine uniformément, DirectionalLight simule le soleil avec ombres douces (PCFSoftShadowMap). Activez shadowMap pour éviter les artefacts ; augmentez mapSize pour netteté sans tuer les perfs.
Ajouter OrbitControls pour interaction
<!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 geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0xff6347 });
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, 0.5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
camera.position.z = 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);
});
</script>
</body>
</html>OrbitControls permet zoom, pan et rotation souris (standard UX 3D). enableDamping ajoute inertie fluide comme dans Blender. Appelez controls.update() dans la boucle ; sans ça, les contrôles laguent sur mobile.
Vers une sphère texturée animée
Prochain niveau : Remplacez le cube par une sphère avec texture (comme une planète). Ajoutons un sol pour les ombres et une animation plus sophistiquée.
Scène complète avec sphère texturée et sol
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Scène Complète Animée</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(0x87CEEB);
scene.fog = new THREE.Fog(0x87CEEB, 10, 100);
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);
// Sol
const groundGeometry = new THREE.PlaneGeometry(20, 20);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x90EE90 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -1;
ground.receiveShadow = true;
scene.add(ground);
// Sphère texturée
const textureLoader = new THREE.TextureLoader();
const sphereGeometry = new THREE.SphereGeometry(0.8, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({
map: textureLoader.load('https://threejs.org/examples/textures/uv_grid_opengl.jpg'),
metalness: 0.1,
roughness: 0.5
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.y = 0.5;
sphere.castShadow = true;
scene.add(sphere);
// Lumières
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
scene.add(directionalLight);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
camera.position.set(0, 2, 5);
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
sphere.rotation.y = elapsedTime * 0.5;
sphere.position.x = Math.sin(elapsedTime * 0.3) * 2;
controls.update();
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>Scène finale : sphère texturée orbitant sur un sol ombré, avec fog pour profondeur. TextureLoader charge une grille UV (remplacez par votre image). Clock permet animations temps-réel fluides ; ajustez shadow.camera pour ombres précises sans clipping.
Bonnes pratiques
- Optimisez les géométries : Utilisez
BufferGeometryUtils.mergeBufferGeometries()pour batches. - Disposez les objets :
geometry.dispose(),material.dispose()ettexture.dispose()anti-fuites mémoire. - Limitez les draw calls : Instanciez meshes avec
InstancedMeshpour 1000+ objets. - Perf monitoring : Ajoutez
Stats.jsetrenderer.infopour FPS/debug. - Mobile-first :
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)).
Erreurs courantes à éviter
- Oublier
controls.update(): Contrôles figés après pause. - Caméra statique : Toujours
camera.lookAt(0,0,0)ou OrbitControls. - Textures non-puissantes : Chargez avec
texture.encoding = THREE.sRGBEncodingpour couleurs justes. - Boucle infinie sans RAF : Utilisez toujours
requestAnimationFramevs timeout.
Pour aller plus loin
Intégrez Three.js à React avec @react-three/fiber ou Next.js. Explorez les loaders GLTF pour modèles 3D complexes. Découvrez nos formations Learni sur WebGL avancé et shaders personnalisés. Docs officielles : threejs.org.