De plus en plus, les interfaces Web gagnent en finesse. Pour pouvoir se démarquer, une dose subtile de 3D peut être un atout pour vos pages web.
Depuis plusieurs semaines, je travaille pour Web Digital Project, une entreprise me correspondant parfaitement puisque le slogan est le suivant : “WDP, votre partenaire de solutions digitales”.
Étant donné que WDP ne dispose pas encore de page d’accueil, j’ai pensé que créer une page avec un logo interactif serait à la fois un bon moyen de mettre les mains dans la base de Three.js, une bibliothèque Javascript 3D; le tout en le faisant de manière assez rapide puisqu’il s’agit d’une simple page Web.
Three.js en quelques mots
Three.js est une bibliothèque JavaScript qui permet de créer des scènes 3D, facilement lisibles depuis un navigateur web. Elle ne nécessite aucune plugin et fonctionne donc avec n’importe quel navigateur moderne.
Initialisation
Le point de départ est pour le moins basique : un fichier HTML avec 2 balises <script>
à l’intérieur des balises <body>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Votre titre de page</title>
<link rel="stylesheet" href="./css/style.css">
<style>
body { margin: 0; }
</style>
</head>
<body>
<div id="logo-container"></div>
<script src="js/three.js"></script>
<script></script>
</body>
</html>
La balise <head>
est classique, je ne vais pas m’attarder dessus tout de suite bien qu’elle soit utile, on le verra plus tard.
Dans le <body>
, on trouve un <div>
dont l’id est “logo-container”. Ce container contiendra à terme le canvas 3D. Ce même canvas pourrait être ajouté directement sur le <body>
mais l’intégrer à un <div>
permettra de mieux le personnaliser, via CSS
notamment.
La ligne suivante <script src="js/three.js"></script>
correspond à l’ajout de la bibliothèque three.js; la bibliothèque par laquelle toute la magie va s’opérer. Bien sûr, le fichier devra être ajouté, il peut être télécharger sur le site officiel de three.js
Ensuite, vous pouvez constater une balise vide <script></script>
. C’est ici que nous ajouterons le code Javascript exploitant three.js.
Adaptations du logo
Afin de moderniser l’image de WDP un peu plus, j’ai pris la liberté de créer un nouveau logo.
Voici le détail de ma démarche de création, ce n’est pas le but de cet article.
Objectifs à atteindre
Nos fichiers sont en place, notre logo est prêt. Il va être temps de se mettre à coder. Mais coder quoi ?
L’idée est de coder une page avec pour unique contenu le logo interactif, centré. En déplaçant le curseur de la souris, les éléments du logo vont le suivre subtilement. Chaque élément du logo sera superposé et l’arrière-plan circulaire se déplacera de manière autonome. Difficile à imaginer ? Ça va venir.
C’est parti
Pour faciliter la superposition des différents éléments du logo, nous allons créer des images de même dimensions, avec les éléments déjà disposé au bon endroit (chaque cercle au bon endroit, le plan le titre. Je l’avoue, ce n’est pas très propre mais ça permet de ne pas se soucier des coordonnées 3D pour plus tard.
On se retrouve donc avec 5 fichiers images :
Et three.js dans tout ça ?
J’y viens. Nous avons nos fichiers images, nous avons notre page HTML chargeant la bibliothèque Three.js; il est temps de s’y mettre sérieusement.
La scène et la caméra
Premièrement, three.js nécessite une scène. C’est sur cette scène, qui est un espace 3D, que nous allons disposer nos éléments (ici nos images).
var scene = new THREE.Scene();
Tout comme au cinéma, une scène ne serait pas suffisante si on n’y ajoutait aucune caméra. Pour se faire, on procède de la sorte :
var fieldOfView = 75;
var aspectRatio = 1;
var nearPlane = 0.1;
var farPlane = 1000;
var camera = new THREE.PerspectiveCamera(
fieldOfView, aspectRatio, nearPlane, farPlane
);
camera.position.z = 10;
À quoi correspondent ces données ?
fieldOfView
est l’angle de vision (en degrés)aspectRatio
correspond au ratio de votre image. Dans notre cas, j’ai choisi des images carrées, le ratio est donc de 1 (par exemple 800px de largeur / 800px de hauteur = 800/800 = 1)nearPlane
etfarPlane
. Ces 2 variables vont permettre de déterminer les éléments que three.js va modéliser.nearPlane
est la distance du plan le plus proche de la caméra à modéliser,farPlane
celle du plan le plus éloigné. Tout ce qui sera plus près quenearPlan
et plus loin quefarPlane
sera ignoré par three.js.- Une fois ces variables initialisées, nous pouvons créer la caméra (
camera
) en tant que telle, en utilisant les paramètres que nous venons de définir. - Enfin, la camera doit prendre une position. La position est définie en 3D,
x
représente l’axe des abscisses,y
les ordonnées etz
la profondeur. Sauf cas particulier, les axesx
ety
n’ont pas besoin d’être modifiés. Nous allons modifier la profondeur et l’initialiser à 10.
Le moteur de rendu
Une scène, une caméra, … Les spécialistes de la 3D le savent bien, il manque un moteur de rendu.
var renderer = new THREE.WebGLRenderer({antialias: true, alpha:true});
renderer.setSize(800, 800);
document.getElementById('logo-container').appendChild( renderer.domElement );
La première ligne permet de créer le fameux renderer
.
On remarquera 2 options entrées en paramètres : antialias
et alpha
.
- Indiquer
antialias: true
permet de spécifier qu’on souhaite un anti-crénelage. Pour simplifier, les courbes de nos formes vont être lissées. - Indiquer
alpha: true
va permettre de préciser à three.js que nos objets (ici nos images) ont un arrière-plan transparent.
Ensuite, renderer.setSize(800, 800)
va préciser à Three.js que le rendu doit s’effectuer sur un cadre de carré de 800×800.
Enfin, la dernière ligne est une requête Javascript classique qui va permettre d’intégrer ce rendu à la balise <div>
dont l’id est “logo-container”.
Définition des formes
Pour three.js comme pour beaucoup d’outil de création 3D une forme est un maillage (mesh en anglais). Un maillage est une forme géométrique qui dispose d’une certaine texture.
Pour disposer nos images en 3D, nous allons créer autant de plans carré que d’images puis nous allons les superposer.
Pour mettre en place une forme, on procède de la façon suivante :
var loader = new THREE.TextureLoader();
var material = new THREE.MeshLambertMaterial({
map: loader.load('./img/circle.png'),
transparent: true
});
var geometry = new THREE.PlaneGeometry(10, 10);
var mesh = new THREE.Mesh(geometry, material);
loader
va permettre de charger la texture que nous allons associer au plan correspondant.material
est l’objet texture que nous allons associer à la forme 3D (dans notre cas le plan). Nous utilisons comme paramètremap
pour corréler l’image avec la texture; et nous précisons que l’image dispose d’un arrière-plan transparent.- la variable
geometry
va permettre de créer une forme géométrique.THREE.PlaneGeometry(10,10)
indique à Three.js que nous souhaitons créer un plan carré. - Enfin, mesh est notre forme géométrique à proprement parler. On l’initialise via la méthode
THREE.Mesh(geometry, material)
,geometry
précisant la forme de l’objet etmaterial
sa texture.
Grâce à cette portion de code, nous avons créé une forme : le cercle d’arrière-plan. Il nous reste à créer 3 autres plans avec chacune des lettres de notre logo.
Voici donc à quoi pourrait ressembler le bloc complet, distinguant les 4 formes.
var loader = new THREE.TextureLoader();
var loader1 = new THREE.TextureLoader();
var loader2 = new THREE.TextureLoader();
var loader3 = new THREE.TextureLoader();
var loader4 = new THREE.TextureLoader();
var material = new THREE.MeshLambertMaterial({
map: loader.load('./img/plan.png'),
transparent: true
});
var material1 = new THREE.MeshLambertMaterial({
map: loader1.load('./img/w.png'),
transparent: true
});
var material2 = new THREE.MeshLambertMaterial({
map: loader2.load('./img/d.png'),
transparent: true
});
var material3 = new THREE.MeshLambertMaterial({
map: loader3.load('./img/p.png'),
transparent: true
});
var material4 = new THREE.MeshLambertMaterial({
map: loader4.load('./img/title.png'),
transparent: true
});
var geometry = new THREE.PlaneGeometry(10, 10);
var geometry1 = new THREE.PlaneGeometry(10, 10);
var geometry2 = new THREE.PlaneGeometry(10, 10);
var geometry3 = new THREE.PlaneGeometry(10, 10);
var geometry4 = new THREE.PlaneGeometry(10, 10);
var mesh = new THREE.Mesh(geometry, material);
var mesh1 = new THREE.Mesh(geometry1, material1);
var mesh2 = new THREE.Mesh(geometry2, material2);
var mesh3 = new THREE.Mesh(geometry3, material3);
var mesh4 = new THREE.Mesh(geometry4, material4);
Nous disposons maintenant de 5 formes : mesh
, mesh1
, mesh2
, mesh3 et mesh4
. Ces formes sont des objets THREE mais ne sont pas encore disposés sur la scène. Nous allons pallier à cela tout de suite :
scene.add(mesh);
scene.add(mesh1);
scene.add(mesh2);
scene.add(mesh3);
scene.add(mesh4);
Les formes sont ajoutées. Problème : elles sont toutes précisément au même endroit. Le rendu ne sera donc pas propre puisque nous avons 5 formes identiques (bien que les textures soient différentes) disposées au même endroit, avec la même orientation. Nous allons donc légèrement modifier la position sur l’axe z
pour que les formes soient superposées.
mesh.position.set(0,0,0);
mesh1.position.set(0,0,0.2);
mesh2.position.set(0,0,0.4);
mesh3.position.set(0,0,0.6);
mesh4.position.set(0,0,0.8);
Lumière…
Les spécialistes le savent, la lumière est essentielle ! Three.js ne déroge pas à la règle.
var light = new THREE.PointLight( 0xffffff, 1, 0 );
light.position.set(1, 1, 100 );
scene.add(light)
Il existe plusieurs types de points de lumière avec Three.js. Nous utilisons ici THREE.PointLight
. La seconde ligne de code correspond à la position de la source de lumière puis nous concluons cette portion de code par l’ajout de la source lumineuse sur la scène ( scene.add(light)
)
… et … Action !
Le rendu final permettant d’effectivement visualiser nos formes sur le navigateur web passe par la création et l’appel d’une méthode animate()
que voici, à minima :
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
Dans notre cas, elle sera un peu plus complexe. Je souhaite que l’arrière-plan (mesh
) bouge doucement de gauche à droite et de bas en haut. Pour mettre cela en place, je vais adapter la méthode animate
.
Tout d’abord, je vais initialiser 2 variables :
lefttoright
: un booléentrue
lorsque le plan se dirige vers la droite (et vers le haut),false
dans le cas contraire.maxPos
: la position maximale à partir de laquelle la direction du plan sera inversée.
Ensuite, à chaque appel de la méthode animate, c’est-à-dire à chaque fois que la donnée est rafraichie par le navigateur (le plus souvent entre 10 et 25 fois par seconde), la forme va bouger d’une distance infime. Si la position maximale est atteinte, la forme commencera à se déplacer dans l’autre direction.
Ça donne ceci :
var lefttoright = true;
var maxPos = 0.15;
function animate() {
requestAnimationFrame( animate );
if (lefttoright) {
mesh.position.x += 0.002;
mesh.position.y += 0.001;
if (mesh.position.x > maxPos) {
lefttoright = false;
}
}
else {
mesh.position.x -= 0.002;
mesh.position.y -= 0.001;
if (mesh.position.x < -maxPos) {
lefttoright = true;
}
}
renderer.render( scene, camera );
}
animate();
Si vous avez suivi précisément ce processus, vous devriez vous retrouver avec le logo initial devant un plan qui se déplace doucement de gauche à droite et de haut en bas. Beaucoup de difficultés pour un résultat plutôt basique, il est temps d’ajouter un peu d’interactivité à tout ça. Et ce n’est pas si compliqué que ça !
On suit la petite souris !
L’interactivité s’effectuera via le suivi du pointeur de notre souris. Lorsque nous déplaçons le pointeur de souris, chaque lettre du logo se déplace un peu vers lui.
Pour se faire, nous allons créer une méthode qui sera appelée lorsque le navigateur détectera un mouvement de souris.
document.addEventListener('mousemove', onMouseMove, false);
function onMouseMove(event) {
};
À chaque mouvement de souris, nous récupérons l’abscisse et l’ordonnée dans des variables mouseX
et mouseY
. Ensuite, nous déplaçons la caméra en suivant le même mouvement. De la même façon, chacune des formes du logo (hormis l’arrière-plan) va suivre le même mouvement, mais dans une proportion différente.
Enfin, une fois chaque objet positionné, la caméra va pointer vers la position de la scène elle-même.
Concrètement, ça donne ça :
function onMouseMove(event) {
mouseX = event.clientX - window.innerWidth / 2;
mouseY = event.clientY - window.innerHeight / 2;
camera.position.x = (mouseX - camera.position.x) * 0.005;
camera.position.y = (mouseY - camera.position.y) * 0.005;
mesh1.position.x = (mouseX - camera.position.x) * 0.002;
mesh1.position.y = (mouseY - camera.position.y) * 0.002;
mesh2.position.x = (mouseX - camera.position.x) * 0.0025;
mesh2.position.y = (mouseY - camera.position.y) * 0.0025;
mesh3.position.x = (mouseX - camera.position.x) * 0.003;
mesh3.position.y = (mouseY - camera.position.y) * 0.003;
mesh4.position.x = (mouseX - camera.position.x) * 0.0035;
mesh4.position.y = (mouseY - camera.position.y) * 0.0035;
camera.lookAt(scene.position);
};
Nous y sommes
Ajoutons un petit peu de css pour un arrière plan sympa, et pour centrer le logo :
body {
background: rgb(0,0,0);
background: radial-gradient(circle, rgba(0,0,0,1) 0%, rgba(84,84,84,1) 100%);
margin: 0;
}
#logo-container {
display: block;
width: 800px;
height: 800px;
margin-left: auto;
margin-right: auto;
}
Et nous obtenons une page certes basique mais qui fera son effet par la subtilité de son interactivité.
Voici enfin pour le rendu de ce mini tutoriel sur three.js.