threlte logo
@threlte/extras

<CameraControls>

This component is a declarative implementation of the popular camera-controls library.

<script lang="ts">
  import Scene from './Scene.svelte'
  import { Button, Checkbox, Pane, Separator } from 'svelte-tweakpane-ui'
  import { Canvas } from '@threlte/core'
  import type { CameraControlsRef } from '@threlte/extras'
  import { type Mesh, MathUtils } from 'three'

  let controls = $state.raw<CameraControlsRef>()
  let mesh = $state.raw<Mesh>()

  /**
   * controls.enabled can not be bound to since its not reactive
   */
  let enabled = $state(true)
  $effect(() => {
    if (controls !== undefined) {
      controls.enabled = enabled
    }
  })
</script>

<Pane
  title="Camera Controls"
  position="fixed"
>
  <Button
    title="rotate(45deg, 0)"
    on:click={() => {
      controls?.rotate(45 * MathUtils.DEG2RAD, 0, true)
    }}
  />
  <Button
    title="rotate(-90deg, 0)"
    on:click={() => {
      controls?.rotate(-90 * MathUtils.DEG2RAD, 0, true)
    }}
  />
  <Button
    title="rotate(360deg, 0)"
    on:click={() => {
      controls?.rotate(360 * MathUtils.DEG2RAD, 0, true)
    }}
  />
  <Button
    title="rotate(0, 20deg)"
    on:click={() => {
      controls?.rotate(0, 20 * MathUtils.DEG2RAD, true)
    }}
  />
  <Separator />
  <Button
    title="truck(1, 0)"
    on:click={() => {
      controls?.truck(1, 0, true)
    }}
  />
  <Button
    title="truck(0, 1)"
    on:click={() => {
      controls?.truck(0, 1, true)
    }}
  />
  <Button
    title="truck(-1, -1)"
    on:click={() => {
      controls?.truck(-1, -1, true)
    }}
  />
  <Separator />
  <Button
    title="dolly(1)"
    on:click={() => {
      controls?.dolly(1, true)
    }}
  />
  <Button
    title="dolly(-1)"
    on:click={() => {
      controls?.dolly(-1, true)
    }}
  />
  <Separator />
  <Button
    title="zoom(camera.zoom / 2)"
    on:click={() => {
      controls?.zoom(controls.camera.zoom / 2, true)
    }}
  />
  <Button
    title="zoom(-camera.zoom / 2)"
    on:click={() => {
      controls?.zoom(-controls.camera.zoom / 2, true)
    }}
  />
  <Separator />
  <Button
    title="moveTo(3, 5, 2)"
    on:click={() => {
      controls?.moveTo(3, 5, 2, true)
    }}
  />
  <Button
    title="fitToBox(mesh)"
    on:click={() => {
      if (mesh !== undefined) {
        controls?.fitToBox(mesh, true)
      }
    }}
  />
  <Separator />
  <Button
    title="setPosition(-5, 2, 1)"
    on:click={() => {
      controls?.setPosition(-5, 2, 1, true)
    }}
  />
  <Button
    title="setTarget(3, 0, -3)"
    on:click={() => {
      controls?.setTarget(3, 0, -3, true)
    }}
  />
  <Button
    title="setLookAt(1, 2, 3, 1, 1, 0)"
    on:click={() => {
      controls?.setLookAt(1, 2, 3, 1, 1, 0, true)
    }}
  />
  <Separator />
  <Button
    title="lerpLookAt(-2,0,0,1,1,0,0,2,5,-1,0,0,random())"
    on:click={() => {
      controls?.lerpLookAt(-2, 0, 0, 1, 1, 0, 0, 2, 5, -1, 0, 0, Math.random(), true)
    }}
  />
  <Separator />
  <Button
    title="reset()"
    on:click={() => {
      controls?.reset(true)
    }}
  />
  <Button
    title="saveState()"
    on:click={() => {
      controls?.saveState()
    }}
  />
  <Separator />
  <Checkbox
    bind:value={enabled}
    label="enabled"
  />
</Pane>

<Canvas>
  <Scene
    bind:controls
    bind:mesh
  />
</Canvas>
<script lang="ts">
  import { Mesh } from 'three'
  import { T } from '@threlte/core'
  import { Grid, CameraControls, type CameraControlsRef } from '@threlte/extras'

  interface Props {
    controls?: CameraControlsRef
    mesh?: Mesh
  }

  let { controls = $bindable(), mesh = $bindable() }: Props = $props()
</script>

<CameraControls
  bind:ref={controls}
  oncreate={(ref) => {
    ref.setPosition(5, 5, 5)
  }}
/>

<T.Mesh
  bind:ref={mesh}
  position.y={0.5}
>
  <T.BoxGeometry />
  <T.MeshBasicMaterial
    color="#ff3e00"
    wireframe
  />
</T.Mesh>

<Grid
  sectionColor="#ff3e00"
  sectionThickness={1}
  cellColor="#cccccc"
  gridSize={40}
/>

If the controls are set as a child component of a camera, they will attach to that camera.

<T.PerspectiveCamera makeDefault>
  <CameraControls />
</T.PerspectiveCamera>

A camera can also optionally be passed to the controls as a prop.

<CameraControls camera={myPerspectiveCamera} />

Finally, if the component is created without an attached camera it will use the scene’s default camera as provided by useThrelte.

Examples

Basic Example

CameraControls.svelte
<script lang="ts">
  import { CameraControls, type CameraControlsRef } from '@threlte/extras'

  let controls = $state<CameraControlsRef>()

  $effect.pre(() => {
    controls?.truck(1, 0, true)
  })
</script>

<CameraControls
  bind:ref={controls}
  oncreate={(ref) => ref.setPosition(5, 5, 5)}
/>

Prevent SSR Externalization

If you are using SvelteKit or Vite for building your app, you may need to externalize the camera-controls library.

To externalize the camera-controls library put the following in your vite.config.js or vite.config.ts.

// vite.config.ts
export default defineConfig({
  plugins: [sveltekit()],
  ssr: {
    noExternal: ['camera-controls']
  }
})

The camera-controls package features include first-person, third-person, pointer-lock, fit-to-bounding-sphere and much more!

Component Signature

<CameraControls> extends < T . CameraControls > and supports all its props, snippets, bindings and events.

Props

name
type
required
default
description

azimuthAngle
number
no

azimuthRotateSpeed
number
no
1

boundaryEnclosesCamera
boolean
no
false

boundaryFriction
number
no
0

camera
PerspectiveCamera | OrthographicCamera
no
Camera instance to control. Uses the parent camera or Threlte's default camera when unset.

colliderMeshes
Object3D[]
no
[]

distance
number
no

dollyDragInverted
boolean
no
false

dollySpeed
number
no
1

dollyToCursor
boolean
no
false

draggingSmoothTime
number
no
0.125

dragToOffset
boolean
no
false

enabled
boolean
no
true

infinityDolly
boolean
no
false

interactiveArea
DOMRect | { x: number; y: number; width: number; height: number }
no
new DOMRect(0, 0, 1, 1)

maxAzimuthAngle
number
no
Infinity

maxDistance
number
no
Infinity

maxPolarAngle
number
no
Math.PI

maxSpeed
number
no
Infinity

maxZoom
number
no
Infinity

minAzimuthAngle
number
no
-Infinity

minDistance
number
no
Number.EPSILON

minPolarAngle
number
no
0

minZoom
number
no
0.01

mouseButtons
CameraControlsMouseButtons
no

pointerLock
boolean
no
false
Click the canvas to request pointer lock and rotate from pointer movement.

pointerLockSensitivity
number
no
0.003
Radians of camera rotation per pixel of pointer movement while pointer lock is active.

polarAngle
number
no

polarRotateSpeed
number
no
1

restThreshold
number
no
0.01

smoothTime
number
no
0.25

touches
CameraControlsTouches
no

truckSpeed
number
no
2

Bindings

name
type

ref
CameraControlsRef