threlte logo
@threlte/extras

useCursor

A hook that sets the css cursor property according to the hover state of a mesh, so that you can give the user visual feedback.

If a context is present, the cursor property will be set on the DOM element of the renderer, otherwise it will be set on the body 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, useThrelte } from '@threlte/core'
  import { interactivity, Text, useCursor } from '@threlte/extras'
  import { DEG2RAD } from 'three/src/math/MathUtils.js'

  const { hovering, onPointerEnter, onPointerLeave } = useCursor()

  $: color = $hovering ? '#dddddd' : '#FE3D00'

  const { size } = useThrelte()

  let zoom = $size.width / 7
  $: zoom = $size.width / 7

  interactivity()
</script>

<T.OrthographicCamera
  {zoom}
  position={[5, 5, 5]}
  on:create={({ ref }) => {
    ref.lookAt(0, 0, 0)
  }}
  makeDefault
/>

<T.DirectionalLight
  position.y={10}
  position.x={5}
/>
<T.AmbientLight intensity={0.2} />

<Text
  text="HOVER"
  interactive
  on:pointerenter={onPointerEnter}
  on:pointerleave={onPointerLeave}
  fontSize={0.5}
  anchorY="100%"
  anchorX="50%"
  rotation.y={90 * DEG2RAD}
  position.y={1}
  position.x={-1}
  {color}
/>

<T.Mesh
  on:pointerenter={onPointerEnter}
  on:pointerleave={onPointerLeave}
>
  <T.MeshStandardMaterial {color} />
  <T.BoxGeometry args={[2, 2, 2]} />
</T.Mesh>

Examples

Simple Usage

Provide arguments to determine the cursor style. The defaults are'pointer' for onPointerOver and 'auto' for onPointerOut. useCursor returns event handlers that you can use to set the hovering state:

<script lang="ts">
  import { Mesh } from '@threlte/core'
  import { useCursor } from '@threlte/extras'
  import { BoxBufferGeometry, MeshBasicMaterial } from 'three'

  // Set the cursor to 'grab' if the pointer is
  // hovering over the mesh and to 'crosshair'
  // if the pointer is outside the mesh
  const { onPointerEnter, onPointerLeave } = useCursor('grab', 'crosshair')
</script>

<Mesh
  interactive
  on:pointerenter={onPointerEnter}
  geometry={new BoxBufferGeometry(1, 1, 1)}
  material={new MeshBasicMaterial()}
/>

Renaming Event Handlers

You can rename the event handlers to resolve naming conflicts. Additionally Svelte allows binding multiple event handlers to the same event:

<script lang="ts">
  import { Mesh } from '@threlte/core'
  import { useCursor } from '@threlte/extras'
  import { BoxBufferGeometry, MeshBasicMaterial } from 'three'

  const { onPointerEnter: cursorEnter, onPointerLeave: cursorLeave } = useCursor()

  const onPointerEnter = () => {
    console.log('Pointer entered!')
  }
  const onPointerLeave = () => {
    console.log('Pointer left!')
  }
</script>

<Mesh
  interactive
  on:pointerenter={cursorEnter}
  on:pointerenter={onPointerEnter}
  geometry={new BoxBufferGeometry(1, 1, 1)}
  material={new MeshBasicMaterial()}
/>

Store Usage

If you want to implement custom logic, you can use the returned svelte store to set the hovering state:

<script lang="ts">
  import { Mesh } from '@threlte/core'
  import { useCursor } from '@threlte/extras'
  import { BoxBufferGeometry, MeshBasicMaterial } from 'three'

  const { hovering } = useCursor()
</script>

<Mesh
  interactive
  on:pointerenter={() => ($hovering = true)}
  on:pointerleave={() => ($hovering = false)}
  geometry={new BoxBufferGeometry(1, 1, 1)}
  material={new MeshBasicMaterial()}
/>

Change the Cursor Style

Provide svelte stores to change the cursor style also while hovering:

<script lang="ts">
  import { Mesh } from '@threlte/core'
  import { useCursor } from '@threlte/extras'
  import { BoxBufferGeometry, MeshBasicMaterial } from 'three'
  import { writable } from 'svelte/store'

  const onPointerOverCursor = writable('grab')

  const { onPointerEnter, onPointerLeave } = useCursor(onPointerOverCursor)

  // somewhere in your application …
  onPointerOverCursor.set('grabbing')
</script>

<Mesh
  interactive
  on:pointerenter={onPointerEnter}
  geometry={new BoxBufferGeometry(1, 1, 1)}
  material={new MeshBasicMaterial()}
/>