5.0.1

useLoop

useLoop provides a convenient access to the render loop of the TresJS scene.

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.

Usage

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')
})

Priority

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.

onBeforeRender(() => {
  console.log('earlier before render')
}, -10)

onBeforeRender(() => {
  console.log('just before render')
})

onBeforeRender(() => {
  console.log('even closer before render')
}, 10)

Register update callbacks

The most common use of onBeforeRender is to register update callbacks for animations, such as rotating or moving objects in the scene.

<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>

Take Over the Render Loop

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()
  }
})
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.

Custom Rendering Examples

Here are examples showing different custom rendering scenarios:

<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>
When you take over the render loop, you become responsible for:
  • Manually triggering a render
  • Always calling notifySuccess() at the end of your render function
  • Handling conditional rendering logic yourself
  • Managing any post-processing effects
  • Ensuring proper frame timing and performance
The built-in render modes (always, on-demand, manual) will be bypassed when using custom rendering.

Callback Parameters

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

delta
number
Time in seconds since the last frame. Perfect for frame-rate independent animations.
elapsed
number
Total elapsed time in seconds since the render loop started. Useful for time-based effects.
renderer
TresRenderer
The Three.js WebGL renderer instance. Access to all renderer methods and properties.
camera
ComputedRef<Camera | undefined>
The currently active camera in the scene. Reactive reference that updates when camera changes.
scene
ShallowRef<TresScene>
The Three.js scene object containing all 3D objects.
sizes
SizesType
Reactive size information including width, height, and pixel ratio of the canvas.
invalidate
() => void
Function to mark the scene as needing an update in the next frame. Particularly useful in on-demand rendering mode.
advance
() => void
Function to manually advance the render loop by one frame. Especially useful in manual rendering mode.
controls
Ref<TresControl | null>
Reference to the current camera controls (if any). Useful for camera-based animations.
events
EventManager
The event manager instance for handling pointer interactions with 3D objects.

The render Method Parameters

The render method takes a function that receives a single notifySuccess callback parameter:

notifySuccess
() => 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.
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.

Type

Signature
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