@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 theta 45deg"
on:click={() => {
controls?.rotate(45 * MathUtils.DEG2RAD, 0, true)
}}
/>
<Button
title="rotate theta -90deg"
on:click={() => {
controls?.rotate(-90 * MathUtils.DEG2RAD, 0, true)
}}
/>
<Button
title="rotate theta 360deg"
on:click={() => {
controls?.rotate(360 * MathUtils.DEG2RAD, 0, true)
}}
/>
<Button
title="rotate phi 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="move to ( 3, 5, 2)"
on:click={() => {
controls?.moveTo(3, 5, 2, true)
}}
/>
<Button
title="fit to the bounding box of the mesh"
on:click={() => {
if (mesh !== undefined) {
controls?.fitToBox(mesh, true)
}
}}
/>
<Separator />
<Button
title="set position to ( -5, 2, 1 )"
on:click={() => {
controls?.setPosition(-5, 2, 1, true)
}}
/>
<Button
title="look at ( 3, 0, -3 )"
on:click={() => {
controls?.setTarget(3, 0, -3, true)
}}
/>
<Button
title="move to ( 1, 2, 3 ), look at ( 1, 1, 0 )"
on:click={() => {
controls?.setLookAt(1, 2, 3, 1, 1, 0, true)
}}
/>
<Separator />
<Button
title="move to somewhere between ( -2, 0, 0 ) -> ( 1, 1, 0 ) and ( 0, 2, 5 ) -> ( -1, 0, 0 )"
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'
let {
controls = $bindable(),
mesh = $bindable()
}: {
controls?: CameraControlsRef
mesh?: Mesh
} = $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/core'
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!