Skip to content


 Zabbix
 Grafana
 Prometheus
 React Three Fiber
 Threejs and TypeScript
 SocketIO and TypeScript
 Blender Topological Earth
 Sweet Home 3D
 Design Patterns Python
 Design Patterns TypeScript
   
 Course Coupon Codes
Three.js and TypeScript
Kindle Edition
$6.99 $9.99 Paperback 
$22.99 $29.99




Design Patterns in TypeScript
Kindle Edition
$6.99 $9.99 Paperback
$11.99 $19.99




Design Patterns in Python
Kindle Edition
$6.99 $9.99 Paperback
$11.99 $19.99




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'

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/s/9keg6

Html Component Drei
Annotations codesandbox
Computer Screen Example codesandbox
Examples/House sbcode.net

Working Example