threlte logo
@threlte/extras

useProgress

Convenience hook that wraps THREE.DefaultLoadingManager.

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

  const { progress } = useProgress()

  const tweenedProgress = tweened($progress, {
    duration: 150
  })
  $: tweenedProgress.set($progress)
</script>

{#if $tweenedProgress < 1}
  <div
    transition:fade|local={{
      duration: 200
    }}
    class="wrapper"
  >
    <p class="loading">Loading</p>
    <div class="bar-wrapper">
      <div
        class="bar"
        style="width: {$tweenedProgress * 100}%"
      ></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 { T, useTask } from '@threlte/core'
  import { Environment, useGltf } from '@threlte/extras'
  import { derived } from 'svelte/store'
  import type { Material, Mesh } from 'three'

  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
  )

  const helmet = derived(gltf, (gltf) => {
    if (!gltf || !gltf.nodes['node_damagedHelmet_-6514']) return
    return gltf.nodes['node_damagedHelmet_-6514']
  })
</script>

<Environment
  path="/hdr/"
  files="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}>
  {#if $helmet}
    <T is={$helmet} />
  {/if}
</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>