threlte logo
@threlte/extras

<HTML>

This component is a port of drei’s <Html> component. It allows you to tie HTML content to any object of your scene. It will be projected to the objects whereabouts automatically.

The container of your <Canvas> component needs to be set to position: relative | absolute | sticky | fixed. This is because the DOM element will be mounted as a sibling to the <canvas> element.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import Scene from './Scene.svelte'
</script>

<div>
  <Canvas>
    <Scene />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { T } from '@threlte/core'
  import { HTML, OrbitControls } from '@threlte/extras'
  import { spring } from 'svelte/motion'
  import { Color, MeshStandardMaterial } from 'three'
  import { DEG2RAD } from 'three/src/math/MathUtils'

  const getRandomColor = () => `#${Math.floor(Math.random() * 16777215).toString(16)}`

  let material = new MeshStandardMaterial({
    color: new Color(getRandomColor())
  })

  const onClick = () => {
    material.color.set(getRandomColor())
    material = material
  }
  let isHovering = false
  let isPointerDown = false

  let htmlPosZ = spring(0)
  $: htmlPosZ.set(isPointerDown ? -0.15 : isHovering ? -0.075 : 0, {
    hard: isPointerDown
  })
</script>

<T.PerspectiveCamera
  position={[10, 5, 10]}
  makeDefault
  fov={30}
>
  <OrbitControls
    target.y={0.75}
    maxPolarAngle={85 * DEG2RAD}
    minPolarAngle={20 * DEG2RAD}
    maxAzimuthAngle={45 * DEG2RAD}
    minAzimuthAngle={-45 * DEG2RAD}
    enableZoom={false}
  />
</T.PerspectiveCamera>

<T.DirectionalLight position={[0, 10, 10]} />

<T.AmbientLight intensity={0.3} />

<T.GridHelper />

<T.Mesh
  position.y={0.5}
  {material}
>
  <T.SphereGeometry args={[0.5]} />
  <HTML
    position.y={1.25}
    position.z={$htmlPosZ}
    transform
  >
    <button
      on:pointerenter={() => (isHovering = true)}
      on:pointerleave={() => {
        isPointerDown = false
        isHovering = false
      }}
      on:pointerdown={() => (isPointerDown = true)}
      on:pointerup={() => (isPointerDown = false)}
      on:pointercancel={() => {
        isPointerDown = false
        isHovering = false
      }}
      on:click={onClick}
      class="rounded-full bg-orange-500 px-3 text-white hover:opacity-90 active:opacity-70"
    >
      I'm a regular HTML button
    </button>
  </HTML>

  <HTML
    position.x={0.75}
    transform
    pointerEvents="none"
  >
    <p
      class="w-auto translate-x-1/2 text-xs drop-shadow-lg"
      style="color: #{material.color.getHexString()}"
    >
      color: #{material.color.getHexString()}
    </p>
  </HTML>
</T.Mesh>

Examples

Basic Example

<script lang="ts">
  import { HTML } from '@threlte/extras'
</script>

<HTML>
  <h1>Hello World</h1>
</HTML>

Transform

transform applies matrix3d transformations.

<script lang="ts">
  import { HTML } from '@threlte/extras'
</script>

<HTML transform>
  <h1>Hello World</h1>
</HTML>

Occlude

<Html> can be occluded behind geometry using the occlude occlude property.

<script lang="ts">
  import { HTML } from '@threlte/extras'
</script>

<HTML
  transform
  occlude
>
  <h1>Hello World</h1>
</HTML>

Visibility Change Event

Use the property occlude and bind to the event visibilitychange to implement a custom hide/show behaviour.

<script lang="ts">
  import { HTML } from '@threlte/extras'

  const onVisibilityChange = (isVisible: boolean) => {
    console.log(isVisible)
  }
</script>

<HTML
  transform
  occlude
  on:visibilitychange={onVisibilityChange}
>
  <h1>Hello World</h1>
</HTML>

When binding to the event visibilitychange the contents of <HTML> is not automatically hidden when it’s occluded.

Sprite Rendering

Use the property sprite in transform mode to render the contents of <HTML> as a sprite.

<script lang="ts">
  import { HTML } from '@threlte/extras'
</script>

<HTML
  transform
  sprite
>
  <h1>Hello World</h1>
</HTML>

Center

Add a -50%/-50% css transform with center when not in transform mode.

<script lang="ts">
  import { HTML } from '@threlte/extras'
</script>

<HTML center>
  <h1>Hello World</h1>
</HTML>

Portal

Use the property portal to mount the contents of the <HTML> component on another HTMLElement. By default the contents are mounted as a sibling to the rendering <canvas>.

<script lang="ts">
  import { HTML } from '@threlte/extras'
</script>

<HTML portal={document.body}>
  <h1>Hello World</h1>
</HTML>

Component Signature

<HTML> extends <T.Group> and supports all its props, slot props, bindings and events.

Props

name
type
required
default

as
keyof HTMLElementTagNameMap
no
'div'

calculatePosition
( obj: Object3D, camera: Camera, size: { width: number; height: number } ) => [number, number]
no

center
boolean
no
false

distanceFactor
number
no
undefined

eps
number
no
0.001

fullscreen
boolean
no
false

occlude
boolean | Object3D[]
no
false

pointerEvents
'auto' | 'none' | 'visiblePainted' | 'visibleFill' | 'visibleStroke' | 'visible' | 'painted' | 'fill' | 'stroke' | 'all' | 'inherit'
no
'auto'

portal
HTMLElement
no
undefined

sprite
boolean
no
false

transform
boolean
no
false

zIndexRange
[number, number]
no
[16777271, 0]