title: useLoop
The useLoop
composable allows you to register callbacks that run before and after each render cycle, or take complete control of the rendering process within TresCanvas
components.
::dotted-diagram ::diagrams-render-loop :: ::
::warning
useLoop
can only be used in child components of a TresCanvas
component, as its data is provided by TresCanvas
.
::
import { useLoop } from '@tresjs/core'
const { onBeforeRender, onRender } = useLoop()
onBeforeRender(() => {
console.log('before render')
})
onRender(() => {
console.log('after render')
})
The onBeforeRender
and onRender
callbacks can be registered with a priority. The priority is a number that determines the order in which the callbacks are executed. The default priority is 0.
::code-group
onBeforeRender(() => {
console.log('earlier before render')
}, -10)
onBeforeRender(() => {
console.log('just before render')
})
onBeforeRender(() => {
console.log('even closer before render')
}, 10)
onRender(() => {
console.log('even closer after render')
}, -10)
onRender(() => {
console.log('just after render')
})
onRender(() => {
console.log('later after render')
}, 10)
::
The most common use of onBeforeRender
is to register update callbacks for animations, such as rotating or moving objects in the scene.
::code-group
<script setup lang="ts">
import { useLoop } from '@tresjs/core'
const { onBeforeRender } = useLoop()
const cube = ref<Mesh>()
// Frame-rate independent animation using delta time
onBeforeRender(({ delta, elapsed }) => {
if (cube.value) {
// Smooth rotation based on frame time
cube.value.rotation.y += delta * 2 // 2 radians per second
// Oscillating movement based on elapsed time
cube.value.position.y = Math.sin(elapsed * 3) * 0.5
}
})
</script>
<template>
<TresMesh ref="cube">
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshNormalMaterial />
</TresMesh>
</template>
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import AnimatedCube from './AnimatedCube.vue'
</script>
<template>
<TresCanvas>
<AnimatedCube />
</TresCanvas>
</template>
::
You can take complete control of the rendering process by using the render
method from useLoop
. This allows you to implement custom rendering logic, post-processing effects, or conditional rendering.
import { useLoop, useTresContext } from '@tresjs/core'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
// Take over the render loop with custom logic
render((notifySuccess) => {
// Your custom rendering logic here
if (camera.activeCamera.value) {
renderer.instance.render(scene.value, camera.activeCamera.value)
// IMPORTANT: Call notifySuccess() to indicate the frame was rendered successfully
notifySuccess()
}
})
::warning
Success Callback Required: You must call the provided callback (named notifySuccess()
in the example above) to properly notify the render loop that the frame was completed. This is essential for the render modes (always
, on-demand
, manual
) to function correctly.
::
Here are examples showing different custom rendering scenarios:
::code-group
<script setup lang="ts">
import { useLoop, useTresContext } from '@tresjs/core'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
const shouldRender = ref(true)
render((notifySuccess) => {
// Only render when condition is met
if (shouldRender.value && camera.activeCamera.value) {
renderer.instance.render(scene.value, camera.activeCamera.value)
}
// Always call notifySuccess, even if we didn't render
notifySuccess()
})
</script>
<script setup lang="ts">
import { useLoop, useTresContext } from '@tresjs/core'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
// Setup post-processing composer
const composer = new EffectComposer(renderer.instance)
render((notifySuccess) => {
// Initialize composer on first render
if (composer.passes.length === 0 && camera.activeCamera.value) {
const renderPass = new RenderPass(scene.value, camera.activeCamera.value)
composer.addPass(renderPass)
}
// Render with post-processing
composer.render()
// Notify that the frame is complete
notifySuccess()
})
</script>
<script setup lang="ts">
import { useLoop, useTresContext } from '@tresjs/core'
import { WebGLRenderTarget } from 'three'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
const renderTarget = new WebGLRenderTarget(1024, 1024)
render((notifySuccess) => {
if (camera.activeCamera.value) {
// First pass: render to texture
renderer.instance.setRenderTarget(renderTarget)
renderer.instance.render(scene.value, camera.activeCamera.value)
// Second pass: render to screen
renderer.instance.setRenderTarget(null)
renderer.instance.render(scene.value, camera.activeCamera.value)
}
// Notify completion
notifySuccess()
})
</script>
::
::warning When you take over the render loop, you become responsible for:
notifySuccess()
at the end of your render functionThe built-in render modes (always
, on-demand
, manual
) will be bypassed when using custom rendering.
::
Both onBeforeRender
and onRender
callbacks receive a context object containing timing information and access to the TresJS context:
onBeforeRender(({ delta, elapsed, renderer, camera, scene, sizes, invalidate, advance }) => {
// Timing information
console.log('Time since last frame:', delta) // in seconds
console.log('Total elapsed time:', elapsed) // in seconds
// TresJS context access
console.log('Current camera:', camera.value)
console.log('Scene:', scene.value)
console.log('Canvas size:', sizes.width.value, sizes.height.value)
// Control methods
invalidate() // Mark scene for re-render (useful in on-demand mode)
advance() // Manually advance one frame (useful in manual mode)
})
onBeforeRender
and onRender
Parameters:::field-group ::::field{name="delta" type="number"} Time in seconds since the last frame. Perfect for frame-rate independent animations. ::::
::::field{name="elapsed" type="number"} Total elapsed time in seconds since the render loop started. Useful for time-based effects. ::::
::::field{name="renderer" type="TresRenderer"} The Three.js WebGL renderer instance. Access to all renderer methods and properties. ::::
::::field{name="camera" type="ComputedRef"} The currently active camera in the scene. Reactive reference that updates when camera changes. ::::
::::field{name="scene" type="ShallowRef"} The Three.js scene object containing all 3D objects. ::::
::::field{name="sizes" type="SizesType"} Reactive size information including width, height, and pixel ratio of the canvas. ::::
::::field{name="invalidate" type="() => void"} Function to mark the scene as needing an update in the next frame. Particularly useful in on-demand rendering mode. ::::
::::field{name="advance" type="() => void"} Function to manually advance the render loop by one frame. Especially useful in manual rendering mode. ::::
::::field{name="controls" type="Ref"} Reference to the current camera controls (if any). Useful for camera-based animations. ::::
::::field{name="events" type="EventManager"} The event manager instance for handling pointer interactions with 3D objects. :::: :::
render
Method ParametersThe render
method takes a function that receives a single notifySuccess
callback parameter:
:::field-group ::::field{name="notifySuccess" type="() => void"} A callback function that must be called to indicate the frame has been successfully rendered. This is essential for the render loop to function correctly across all render modes. :::: :::
::note
Important: The render
method does NOT receive a context object like onBeforeRender
and onRender
. Instead, use useTres()
to access the renderer, scene, and camera within your render function.
::
function useLoop(): UseLoopReturn
interface UseLoopReturn {
/** Stops the render loop */
stop: () => void
/** Starts the render loop */
start: () => void
/** Reactive reference indicating if the loop is currently active */
isActive: Ref<boolean>
/** Register a callback to run before each render */
onBeforeRender: (fn: LoopCallback, priority?: number) => { off: () => void }
/** Register a callback to run after each render */
onRender: (fn: LoopCallback, priority?: number) => { off: () => void }
/** Take complete control over the rendering process */
render: (fn: RenderFunction) => void
}
type LoopCallback = (context: LoopContext) => void | Promise<void>
type RenderFunction = (notifySuccess: () => void) => void
interface LoopContext {
/** Time in seconds since the last frame */
delta: number
/** Total elapsed time in seconds since render loop started */
elapsed: number
/** The Three.js WebGL renderer instance */
renderer: TresRenderer
/** The currently active camera */
camera: ComputedRef<Camera | undefined>
/** The Three.js scene object */
scene: ShallowRef<TresScene>
/** Reactive size information for the canvas */
sizes: SizesType
/** Reference to current camera controls */
controls: Ref<TresControl | null>
/** TresJS extension function */
extend: (objects: any) => void
/** Event manager for pointer interactions */
events: EventManager
/** Mark scene for re-render in on-demand mode */
invalidate: () => void
/** Manually advance one frame in manual mode */
advance: () => void
}
interface SizesType {
/** Canvas width in pixels */
width: Ref<number>
/** Canvas height in pixels */
height: Ref<number>
/** Canvas aspect ratio (width / height) */
aspectRatio: Ref<number>
/** Device pixel ratio */
pixelRatio: Ref<number>
}
type TresRenderer = WebGLRenderer | Renderer