threlte logo
@threlte/extras

useProgress

Convenience hook that wraps THREE.DefaultLoadingManager.

<script lang="ts">
  import Scene from './Scene.svelte'
  import { Canvas } from '@threlte/core'
  import { Tween } from 'svelte/motion'
  import { fade } from 'svelte/transition'
  import { fromStore } from 'svelte/store'
  import { useProgress } from '@threlte/extras'

  const { progress } = useProgress()
  const p = fromStore(progress)

  const tweenedProgress = Tween.of(() => p.current, {
    duration: 150
  })

  const progressWidth = $derived(100 * tweenedProgress.current)
  const progressLessThanOne = $derived(tweenedProgress.current < 1)
</script>

{#if progressLessThanOne}
  <div
    transition:fade={{
      duration: 200
    }}
    class="wrapper"
  >
    <p class="loading">Loading</p>
    <div class="bar-wrapper">
      <div
        class="bar"
        style="width: {progressWidth}%"
      ></div>
    </div>
  </div>
{/if}

<div class="main">
  <Canvas>
    <Scene />
  </Canvas>
</div>

<style>
  div.main {
    height: 100%;
  }
  .wrapper {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    background-color: white;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    align-items: center;
    justify-content: center;
    color: black;
  }

  .loading {
    font-size: 0.875rem;
    line-height: 1.25rem;
  }

  .bar-wrapper {
    width: 33.333333%;
    height: 10px;
    border: 1px solid black;
    position: relative;
  }

  .bar {
    height: 100%;
    background-color: black;
  }
</style>
<script lang="ts">
  import type { Material, Mesh } from 'three'
  import { Environment, useGltf } from '@threlte/extras'
  import { T, useTask } from '@threlte/core'

  let rotation = 0
  useTask((delta) => {
    const f = 1 / 60 / delta // ~1 at 60fps
    rotation += 0.01 * f
  })

  type GLTFResult = {
    nodes: {
      'node_damagedHelmet_-6514': Mesh
    }
    materials: {
      Material_MR: Material
    }
  }

  const gltf = useGltf<GLTFResult>(
    '/models/helmet/DamagedHelmet.gltf?v=' + Math.random().toString() // force a reload on every pageload
  )
</script>

<Environment url="/textures/equirectangular/hdr/shanghai_riverside_1k.hdr" />

<T.PerspectiveCamera
  makeDefault
  position.z={10}
  fov={20}
/>

<T.DirectionalLight
  position.y={10}
  position.z={10}
/>

<T.Group rotation.y={rotation}>
  {#await gltf then { nodes }}
    <T is={nodes['node_damagedHelmet_-6514']} />
  {/await}
</T.Group>

Model: Battle Damaged Sci-fi Helmet by theblueturtle_

Examples

Basic Example

You can use and place this hook anywhere. Typically you would use this hook outside of your <Canvas> component to show a loading indicator in your DOM.

<script lang="ts">
  // `useProgress` returns readable stores
  const {
    active, // Readable<boolean> – if the DefaultLoadingManager is active
    item, // Readable<string | undefined> – the currently loading item
    loaded, // Readable<number> - amount of items loaded
    total, // Readable<number> - total amount of items to load
    errors, // Readable<string[]> - all error messages
    progress, // Readable<number> - normalized (0-1) loading progress
    finishedOnce // Readable<boolean> – whether a progress of 1 has been achieved ever.
  } = useProgress()
</script>