Skip to content

Annotations

Import

import { Html } from '@react-three/drei'

Video Lecture

Section Video Links
Annotations : Part 1 Annotations : Part 1 Annotations : Part 1
Annotations : Part 2 Annotations : Part 2 Annotations : Part 2

Description

We will use the Drei HTML component to add annotations to our 3D models.

We will add placeholders, containing text, inside our models using Blender, and then read the userData while we load the model into the scene. For each userData.prop that we find, we will add a new HTML label in that position.

Note

The Drei Html component must be placed inside the React Three Fiber Canvas element. Any HTML tags that you also place within your Drei Html component, will automatically be rendered via the React-Dom reconciler instead despite the HTML tags now existing under the Canvas element.

So using the Drei Html inside a React Three Fiber canvas is another way to solve the HTML Element is not part of the THREE namespace! error.

If you don't want to edit your models in Blender, then you can download the final models used in this lesson from annotations.zip and extract the contents into your projects ./public/models/ folder.

./src/styles.css

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
html,
body,
#root {
  height: 100%;
  margin: 0;
  background: #000000;
}

#info {
  position: absolute;
  top: 10px;
  right: 295px;
  text-align: right;
  background: #181c20;
  color: #8c92a4;
  padding: 7px 10px;
  border-radius: 10px;
  font-size: 17px;
  pointer-events: none;
  font-family: var(--leva-fonts-mono);
}

.annotation {
  transform: translate3d(calc(50%), calc(-50%), 0);
  text-align: left;
  background: #181c20;
  color: #8c92a4;
  padding: 10px 15px;
  border-radius: 5px;
  user-select: none;
  font-family: var(--leva-fonts-mono);
}

.annotation::before {
  content: '';
  position: absolute;
  top: 20px;
  left: -30px;
  height: 2px;
  width: 30px;
  background: #181c20;
}

./src/models.json

1
2
3
4
5
{
  "hammer": "/models/hammer.glb",
  "drill": "/models/drill.glb",
  "tapeMeasure": "/models/tapeMeasure.glb"
}

./src/App.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import { useState } from 'react'
import { Canvas } from '@react-three/fiber'
import {
  useGLTF,
  OrbitControls,
  Environment,
  Stats,
  Html,
} from '@react-three/drei'
import { useControls } from 'leva'
import Models from './models.json'

function Model({ url }) {
  const { scene } = useGLTF(url)
  const [cache, setCache] = useState({})

  if (!cache[url]) {
    const annotations = []

    scene.traverse((o) => {
      if (o.userData.prop) {
        annotations.push(
          <Html
            key={o.uuid}
            position={[o.position.x, o.position.y, o.position.z]}
            distanceFactor={0.25}
          >
            <div className="annotation">{o.userData.prop}</div>
          </Html>
        )
      }
    })

    console.log('Caching JSX for url ' + url)
    setCache({
      ...cache,
      [url]: <primitive object={scene}>{annotations}</primitive>,
    })
  }
  return cache[url]
}

export default function App() {
  const { model } = useControls({
    model: {
      value: 'hammer',
      options: Object.keys(Models),
    },
  })

  return (
    <>
      <Canvas camera={{ position: [0, 0, -0.2], near: 0.025 }}>
        <Environment files="/img/workshop_1k.hdr" background />
        <group>
          <Model url={Models[model]} />
        </group>
        <OrbitControls autoRotate />
        <Stats />
      </Canvas>
      <span id="info">
        The {model.replace(/([A-Z])/g, ' $1').toLowerCase()} is selected.
      </span>
    </>
  )
}

If you want to hide the HTML label whenever it goes behind the geometry, then you can add the occlude option.

<Html position={[0, 0, -1]} occlude>
  <div>this text will occlude if position obstructed</div>
</Html>

This will use a raycaster internally to check if at any time there is any geometry between the current position of the HTML and the position of the camera.

The HTML will always face the camera. If you want it to rotate with the scene, then you can add the transform option.

<Html position={[0, 0, -2]} transform>
  <div>this text will rotate with the scene</div>
</Html>

This is useful if you wanted to simulate a computer screen in your scene. E.g., https://codesandbox.io/p/sandbox/9keg6

Working Example

<>
Annotations (Working Example) sbedit.net
Drei Html Component github.com
Computer Screen Example codesandbox.io
Examples/House sbcode.net

GitHub Branch

git clone https://github.com/Sean-Bradley/React-Three-Fiber-Boilerplate.git
cd React-Three-Fiber-Boilerplate
git checkout annotations
npm install
npm start

Comments