byteleaf logo
← zurück zum Blog

Three.js in React verwenden

Three.js ist die bekannteste Bibliothek zur Einbettung von 3D-Inhalten in Webseiten. Mit React‑three‑fiber ist es möglich, Three.js in eine bestehende React-Anwendung zu integrieren und die finale Website mit 3D-Elementen und Animationen zu beleben.
Paul Tolstoi
Paul Tolstoi
3. September 2021

Was ist Three.js?

Moderne Webseiten sind schon lange nicht mehr nur eine Plattform zur Darstellung von Text und Bildern, sie bieten neben Interaktionen auch dynamische Inhalte an. Zu Zeiten des Flash-Players konnte man Besucher nicht nur mit 2D-Animationen erfreuen, sondern ihnen durchaus auch komplexere Visualisierung und Spiele in 3D bieten. Die Bedeutung von Flash ist jedoch im Webbereich immer weiter zurückgegangen, da die Browser mittlerweile die meisten Funktionalitäten nativ unterstützen. Seit über zehn Jahren gibt es den WebGL-Standard, der beschreibt, wie man eine 3D-Welt in den Browser bringen kann.

Im letzten Jahrzehnt sind einige Frameworks und Bibliotheken entstanden, welche Entwickler unterstützen, die Komplexität von WebGL zu bändigen. Im Bereich der 3D-Grafik ist wohl eine der bekanntesten Frameworks Three.js, welches einige Abstraktionen mit sich bringt:

  • Der Szenengraph, verwaltet die Beziehnungen zwischen einzelnen Objekten in der Szene. So muss man nicht nur jeden Würfel einzeln bewegen, sondern kann diese gruppieren und nur die Gruppe bewegen.
  • Eine Kamera öffnet ein Fenster in die 3D-Welt. Sie kann grundverschiedene Eigenschaften aufweisen: z. B. orthographisch, perspektivisch oder stereo.
  • Geometrie (zur Verfügung stehen z. B. Würfel, Kugeln, Ebenen oder auch Text-Elemente), die man in die Szene setzten kann, beschreibt die Form eines Objekts.
  • Mit Licht kann man verschiedene Lichtquellen simulieren, die man aus dem realem Leben kennt.
  • Jedes sichtbare Objekt hat ein eigenes Material, das verschiedene Eigenschaften beschreibt, z. B. Farbe oder Texture.
  • Mit der Hilfe eines Loaders kann man unterschiedliche Modelle verschiedenster Formate geladen werden, um diese in die Szene einzubetten.
  • Auch AR und VR ist mit dem WebXR-Standard möglich.
  • Um die Immersion noch weiter zu erhöhen, wird auch Audio unterstützt.
  • Tools & Hilfsfunktionen sollen dem Entwickler z. B. beim Berechnen von Rotationen, beim Interpolieren oder Animieren die Arbeit abnehmen.

Three.js Beispielanwendung

Ein einfaches Beispiel benötigt einige Komponenten:

  • Ein Objekt mit einem Material, damit es überhaupt etwas zu sehen gibt.
  • Ein Licht, welches das Objekt beleuchtet.
  • Die Kamera, durch die der Benutzer die Szene sehen kann.
  • Einen Renderer, der durch die Kamera ein Bild vom beleuchteten Objekt erstellt.

Folgender Code-Schnipsel rendert einen Würfel:

1import * as THREE from "three";
2
3// Erstellt eine Szene
4const scene = new THREE.Scene();
5
6// Erstellt eine perspektivische Kamera
7const camera = new THREE.PerspectiveCamera(
8  70,
9  window.innerWidth / window.innerHeight,
10  0.01,
11  10
12);
13
14// Bewegt die Kamera nach oben und hinten
15camera.position.set(0, 5, 5);
16
17// Rotiert die Kamera, so dass sie etwas nach unten zeigt,
18// damit wir mehrere Flächen vom Würfel sehen können
19camera.rotation.x = -45 * THREE.MathUtils.DEG2RAD;
20
21// Erstellt die Würfelgeometrie
22const geometry = new THREE.BoxGeometry(1, 1, 1);
23
24// Erstellt ein einfaches Material für den Würfel
25const material = new THREE.MeshPhongMaterial({ color: "rgb(10,180,180)" });
26
27// Erstellt das eigentliche Mesh-Objekt, mit der Geometrie und dem Material
28const mesh = new THREE.Mesh(geometry, material);
29
30// Rotiert den Würfel, so dass eine Ecke in die Kamera schaut
31mesh.rotation.y = 45 * THREE.MathUtils.DEG2RAD;
32
33// Fügt den Würfel in die Szene ein
34scene.add(mesh);
35
36// Erstellt ein weißes Punktlicht
37const light = new THREE.PointLight("white", 1, 100);
38
39// Verschiebt das Licht, damit die Flächen des Würfels unterschiedlich
40// beleuchtet sind
41light.position.set(5, 10, 10);
42
43// Fügt das Licht in die Szene ein
44scene.add(light);
45
46// Erstellt einen WebGL-Renderer
47const renderer = new THREE.WebGLRenderer();
48
49// Setzt die Größe auf die des Fensters
50renderer.setSize(window.innerWidth, window.innerHeight);
51
52// Fügt das canvas-Element in das Dokument ein
53document.body.appendChild(renderer.domElement);
54
55// Rendert die Scene mit der Kamera ein einziges Mal
56renderer.render(scene, camera);

Das Ergebnis sieht folgendermaßen aus:

threejs-cube.png

Three.js in React verwenden

Wenn man allerdings Three.js in eine bestehende React-Anwendung einbinden will, gestaltet sich das als etwas aufwändiger, da alle Objekte in Three.js einen eigenen Lebenszyklus haben, der unabhängig von React ist. Eine Abhilfe dafür schafft aber react-three-fiber, ein Three.js Renderer für React. react-three-fiber bindet Three.js nur in React ein, der Rest wird nach wie vor von Three.js gestemmt. Die Bibliothek erlaubt dem Entwickler, die Szene mit den in React üblichen Komponenten zu beschreiben und diese wiederzuverwenden. Ein Mesh-Objekt, das aus einer Geometrie und Material besteht, kann durch die JSX-Syntax folgendermaßen ausgedrückt werden.

1<mesh rotation={[45, 0, 0]}>
2  <boxGeometry args={[1, 1, 1]} />
3  <meshPhongMaterial color={'rgb(10,180,180)'} />
4</mesh>

Der Code erstellt genau wie das vorherige Würfelbeispiel einen Würfel mit einem Material, allerdings in einer wesentlich kompakteren und übersichtlicheren Form.

React & Three.js Beispielanwendung

Folgendes Beispiel beschreibt eine etwas komplexere Szene. Die Szene zeigt ein sich drehendes byteleaf-Logo, das mit einer kurzen Animation erscheint:

byteleaf-logo.git
1import React, { Suspense } from "react";
2
3import { Canvas } from "@react-three/fiber";
4import { Logo } from "./Logo";
5import { AppearingRotatingGroup } from "./AppearingRotatingGroup";
6
7export default () => (
8  // Im canvas-Element wird die Three.js-Szene gerendert
9  <Canvas>
10    {/*
11    Fügt ein Umgebungslicht in die Szene, das alle Objekte von allen
12    */}
13    <ambientLight intensity={0.25} />
14    {/*
15    Fügt ein Punktlicht hinzu
16    */}
17    <pointLight position={[10, 10, 10]} intensity={0.5} />
18    {/*
19    Für das dynamische Laden muss React's Suspense-Mechanismus verwendet werden
20    */}
21    <Suspense fallback={null}>
22      {/*
23      Eigene Komponente, die die Kinder-Element zu begin
24      drehend hochskaliert, für eine schöne Erscheinungsanimation
25      */}
26      <AppearingRotatingGroup>
27        {/*
28        Unser Logo
29        */}
30        <Logo />
31      </AppearingRotatingGroup>
32    </Suspense>
33  </Canvas>
34);

Die AppearingRotationGroup kapselt die Kinder-Elemente in eine Gruppe und animiert sie. Das ermöglicht ein hohes Maß an Wiederverwendbarkeit. Der Code sieht folgendermaßen aus:

1import React, { useRef } from "react";
2import { Group, MathUtils } from "three";
3import { useFrame } from "@react-three/fiber";
4
5export const AppearingRotatingGroup: React.FC = ({ children }) => {
6  // Eine Referenz für die Gruppe, damit diese animiert werden kann
7  // null! ist ein kleiner TypeScript-Hack,
8  // damit wir nicht immer auf null überprüfen müssen, da es garantiert ist,
9  // dass die Referenz in useFrame nie null ist
10  const group = useRef<Group>(null!);
11  
12  // Die Rotationsgeschwindigkeit, die in jedem Frame verringert wird
13  const rotationSpeed = useRef(5);
14  
15  // Jeden Frame wird die Gruppe animiert
16  useFrame((state, delta) => {
17  
18    // Dreht die Gruppe um die vertikale Achse
19    group.current.rotation.y -= delta * rotationSpeed.current;
20  
21    // Die Skalierung fängt an bei 0 und wird auf 1 hoch animiert
22    const scale = MathUtils.lerp(
23      group.current.scale.x,
24      1,
25      delta * rotationSpeed.current
26    );
27  
28    group.current.scale.setScalar(scale);
29  
30    // verringert die Rotationsgeschwindigkeit gegen 0 damit diese langsamer wird
31    // und irgendwann gänzlich aufhört
32    rotationSpeed.current = MathUtils.lerp(rotationSpeed.current, 0, delta);
33});
34
35  return (
36    <group ref={group} scale={0}>
37      {children}
38    </group>
39  );
40};

Und schließlich unser Logo als Komponente. Das Logo ist eine GLTF-Datei, die wir laden. Anschließend versehen wir das Logo noch mit einer kleinen Drehanimation.

1import React, { useRef } from "react";
2import { Mesh } from "three";
3import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
4
5import { useFrame, useLoader } from "@react-three/fiber";
6
7export const Logo: React.VFC = () => {
8    
9  // Eine Referenz zum Logo, damit die Drehung animiert werden kann
10  const primitiveRef = useRef<Mesh>(null!);
11  
12  // Das Logo-Objekt wird jeden Frame etwas gedreht
13  useFrame((state, delta) => {
14    primitiveRef.current.rotation.y -= delta;
15  });
16  
17  // Ein React Hook, der das Logo aus einer Datei lädt
18  const logo = useLoader(GLTFLoader, "./logo3d.glb");
19  
20  // Das eigentlich Objekt
21  return <primitive ref={primitiveRef} object={logo.scene} />;
22};

Das Ergebnis sieht folgendermaßen aus:

Fazit

Wie man an den beiden Beispielen sieht, bietet react-three-fiber nicht nur eine einfache Integration von Three.js in eine React-Anwendung, sondern erhöht die Wiederverwendbarkeit und verkürzt damit erheblich die Code-Menge. Auch können die 3D-Komponenten, wie es in einer React-Anwendung üblich ist, getestet werden.

Aber: Man muss bei Animationen immer auf die Performance achten, damit diese flüssig sind und nicht ruckeln. Für eine Animation mit 60 FPS darf also jeder Frame nur knapp 16 Millisekunden brauchen. Wenn man nun also anfängt mit dieser Rate alle Komponenten durch den React-Renderer zu jagen, könnte das zu Performance-Engpässen führen. Deswegen rät die Dokumentation von react-three-fiber davon ab, Animationen mit Hilfe der React-üblichen Konstrukte (z. B. useState) umzusetzen. Stattdessen wird die Verwendung vom useFrame-Hook oder von Bibliotheken wie z. B. react-spring empfohlen (siehe den Performance Stolperfallen Link in der offiziellen Dokumentation).

Neben der Einarbeitung in Three.js muss man sich auch in react-three-fiber einarbeiten, das neben den schon genannten Hooks auch die JSX-Syntax beinhaltet.

Alles in allem bietet react-three-fiber jedoch eine gute Möglichkeit Three.js in eine React-Anwendung einzubetten.

Links

Kontakt

E-Mail

info@byteleaf.de

Telefon

+49 89 21527370

Links

Code

GitHub

Wo wir sind

Adresse

byteleaf GmbH
Barthstraße 16
80339 München

ImpressumDatenschutzCookie-Einstellungen
© 2021 - Code: byteleaf - Design: Michael Motzek