Conseils

Cet article est une collection de petits problèmes que vous pourriez rencontrer en utilisant three.js, qui semblaient trop mineurs pour avoir leur propre article.


Faire une capture d'écran du Canvas

Dans le navigateur, il y a effectivement 2 fonctions qui permettent de prendre une capture d'écran. L'ancienne canvas.toDataURL et la nouvelle, meilleure, canvas.toBlob

On pourrait donc penser qu'il serait facile de prendre une capture d'écran en ajoutant simplement du code comme

<canvas id="c"></canvas>
+<button id="screenshot" type="button">Enregistrer...</button>
const elem = document.querySelector('#screenshot');
elem.addEventListener('click', () => {
  canvas.toBlob((blob) => {
    saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
  });
});

const saveBlob = (function() {
  const a = document.createElement('a');
  document.body.appendChild(a);
  a.style.display = 'none';
  return function saveData(blob, fileName) {
     const url = window.URL.createObjectURL(blob);
     a.href = url;
     a.download = fileName;
     a.click();
  };
}());

Voici l'exemple de l'article sur la réactivité avec le code ci-dessus ajouté et un peu de CSS pour positionner le bouton

Lorsque je l'ai essayé, j'ai obtenu cette capture d'écran

Oui, c'est juste une image noire.

Il est possible que cela ait fonctionné pour vous selon votre navigateur/OS mais en général, il est peu probable que cela fonctionne.

Le problème est que, pour des raisons de performance et de compatibilité, par défaut, le navigateur efface le buffer de dessin d'un canvas WebGL après y avoir dessiné.

La solution est d'appeler votre code de rendu juste avant la capture.

Dans notre code, nous devons ajuster quelques éléments. D'abord, séparons le code de rendu.

+const state = {
+  time: 0,
+};

-function render(time) {
-  time *= 0.001;
+function render() {
  if (resizeRendererToDisplaySize(renderer)) {
    const canvas = renderer.domElement;
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
  }

  cubes.forEach((cube, ndx) => {
    const speed = 1 + ndx * .1;
-    const rot = time * speed;
+    const rot = state.time * speed;
    cube.rotation.x = rot;
    cube.rotation.y = rot;
  });

  renderer.render(scene, camera);

-  requestAnimationFrame(render);
}

+function animate(time) {
+  state.time = time * 0.001;
+
+  render();
+
+  requestAnimationFrame(animate);
+}
+requestAnimationFrame(animate);

Maintenant que render ne s'occupe que du rendu effectif, nous pouvons l'appeler juste avant de capturer le canvas.

const elem = document.querySelector('#screenshot');
elem.addEventListener('click', () => {
+  render();
  canvas.toBlob((blob) => {
    saveBlob(blob, `screencapture-${canvas.width}x-${canvas.height}.png`);
  });
});

Et maintenant, ça devrait marcher.

Pour une solution différente, voir l'élément suivant.


Empêcher l'effacement du canvas

Supposons que vous vouliez permettre à l'utilisateur de peindre avec un objet animé. Vous devez passer preserveDrawingBuffer: true lorsque vous créez le WebGLRenderer. Cela empêche le navigateur d'effacer le canvas. Vous devez également dire à three.js de ne pas effacer le canvas.

const canvas = document.querySelector('#c');
-const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
+const renderer = new THREE.WebGLRenderer({
+  canvas,
+  preserveDrawingBuffer: true,
+  alpha: true,
+});
+renderer.autoClearColor = false;

Notez que si vous étiez sérieux au sujet de la création d'un programme de dessin, ce ne serait pas une solution car le navigateur effacera toujours le canvas chaque fois que sa résolution change. Nous modifions sa résolution en fonction de sa taille d'affichage. Sa taille d'affichage change lorsque la fenêtre change de taille. Cela inclut lorsque l'utilisateur télécharge un fichier, même dans un autre onglet, et que le navigateur ajoute une barre d'état. Cela inclut également lorsque l'utilisateur tourne son téléphone et que le navigateur passe du mode portrait au mode paysage.

Si vous vouliez vraiment créer un programme de dessin, vous devriez faire le rendu sur une texture en utilisant une cible de rendu.


Obtenir la saisie clavier

Tout au long de ces tutoriels, nous avons souvent attaché des écouteurs d'événements au canvas. Bien que de nombreux événements fonctionnent, un qui ne fonctionne pas par défaut est l'événement clavier.

Pour obtenir les événements clavier, définissez le tabindex du canvas à 0 ou plus. Par exemple :

<canvas tabindex="0"></canvas>

Cela finit cependant par causer un nouveau problème. Tout élément ayant un tabindex défini sera mis en évidence lorsqu'il aura le focus. Pour résoudre ce problème, définissez son contour de focus CSS à none (aucun)

canvas:focus {
  outline:none;
}

Pour démontrer, voici 3 canvas

<canvas id="c1"></canvas>
<canvas id="c2" tabindex="0"></canvas>
<canvas id="c3" tabindex="1"></canvas>

et un peu de css juste pour le dernier canvas

#c3:focus {
    outline: none;
}

Notez que vous ne pouvez pas faire en sorte que le premier canvas accepte les saisies clavier. Le deuxième canvas le peut, mais il est mis en évidence. Le 3ème canvas a les deux solutions appliquées.


Rendre le Canvas Transparent

Par défaut, THREE.js rend le canvas opaque. Si vous voulez que le canvas soit transparent, passez alpha:true lorsque vous créez le WebGLRenderer

const canvas = document.querySelector('#c');
-const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
+const renderer = new THREE.WebGLRenderer({
+  canvas,
+  alpha: true,
+});

Vous voudrez probablement aussi lui dire que vos résultats n'utilisent pas l'alpha prémultiplié

const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({
  canvas,
  alpha: true,
+  premultipliedAlpha: false,
});

Three.js utilise par défaut premultipliedAlpha: true pour le canvas, mais par défaut, les matériaux génèrent premultipliedAlpha: false.

Si vous souhaitez mieux comprendre quand utiliser ou non l'alpha prémultiplié, voici un bon article à ce sujet.

En tout cas, configurons un exemple simple avec un canvas transparent.

Nous avons appliqué les paramètres ci-dessus à l'exemple de l'article sur la réactivité. Rendons également les matériaux plus transparents.

function makeInstance(geometry, color, x) {
-  const material = new THREE.MeshPhongMaterial({color});
+  const material = new THREE.MeshPhongMaterial({
+    color,
+    opacity: 0.5,
+  });

...

Et ajoutons un peu de contenu HTML

<body>
  <canvas id="c"></canvas>
+  <div id="content">
+    <div>
+      <h1>Cubes-R-Us !</h1>
+      <p>Nous fabriquons les meilleurs cubes !</p>
+    </div>
+  </div>
</body>

ainsi qu'un peu de CSS pour placer le canvas devant

body {
    margin: 0;
}
#c {
    width: 100%;
    height: 100%;
    display: block;
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 2;
+    pointer-events: none;
}
+#content {
+  font-size: 7vw;
+  font-family: sans-serif;
+  text-align: center;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}

Notez que pointer-events: none rend le canvas invisible aux événements de souris et tactiles afin que vous puissiez sélectionner le texte en dessous.


Faire de votre arrière-plan une animation three.js

Une question courante est de savoir comment faire en sorte qu'une animation three.js serve d'arrière-plan à une page web.

Il y a 2 façons évidentes.

  • Définir la propriété CSS position du canvas sur fixed comme dans
#c {
 position: fixed;
 left: 0;
 top: 0;
 ...
}

Vous pouvez voir cette solution exacte dans l'exemple précédent. Il suffit de définir z-index à -1 et les cubes apparaîtront derrière le texte.

Un petit inconvénient de cette solution est que votre JavaScript doit s'intégrer à la page et si vous avez une page complexe, vous devez vous assurer qu'aucun des scripts JavaScript de votre visualisation three.js n'entre en conflit avec le JavaScript effectuant d'autres tâches dans la page.

  • Utiliser une iframe

C'est la solution utilisée sur la page d'accueil de ce site.

Dans votre page web, insérez simplement une iframe, par exemple :

<iframe id="background" src="responsive.html">
<div>
  Votre contenu ici.
</div>

Ensuite, stylisez l'iframe pour qu'elle remplisse la fenêtre et soit en arrière-plan, ce qui est essentiellement le même code que nous avons utilisé ci-dessus pour le canvas, sauf que nous devons également définir border à none car les iframes ont une bordure par défaut.

#background {
    position: fixed;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    z-index: -1;
    border: none;
    pointer-events: none;
}