Three.js Custom Loading Screen with DefaultLoadingManager

This article explains how to implement a custom, dynamic loading screen in a Three.js application using the global THREE.DefaultLoadingManager. You will learn how to set up the HTML/CSS loader interface and link it to Three.js load events so that a progress bar updates automatically as 3D models, textures, and other assets load, before smoothly fading out once loading is complete.


1. Create the HTML and CSS Loading Screen

First, build a simple overlay that covers the entire viewport. This overlay will contain a progress bar that scales from 0% to 100% based on asset loading progress.

<!-- Loading Screen Overlay -->
<div id="loading-screen">
  <div class="loader-container">
    <h1>Loading Experience...</h1>
    <div class="progress-bar-container">
      <div id="progress-bar"></div>
    </div>
  </div>
</div>

Style the overlay with CSS so that it sits on top of your WebGL canvas. Use transitions to handle a smooth fade-out animation when loading finishes.

#loading-screen {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #111111;
  z-index: 9999;
  display: flex;
  justify-content: center;
  align-items: center;
  transition: opacity 0.5s ease-out;
  opacity: 1;
}

#loading-screen.fade-out {
  opacity: 0;
  pointer-events: none;
}

.loader-container {
  text-align: center;
  color: #ffffff;
  font-family: sans-serif;
}

.progress-bar-container {
  width: 300px;
  height: 10px;
  background-color: #333;
  border-radius: 5px;
  margin-top: 20px;
  overflow: hidden;
}

#progress-bar {
  width: 100%;
  height: 100%;
  background-color: #00ff88;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.1s ease-out;
}

2. Configure the DefaultLoadingManager in JavaScript

THREE.DefaultLoadingManager is a built-in global instance of THREE.LoadingManager. Any standard Three.js loader (such as GLTFLoader, TextureLoader, or FBXLoader) automatically uses this manager unless you explicitly assign a different one.

To update your loading screen, define callback functions on THREE.DefaultLoadingManager before you begin loading any assets.

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

// Get DOM elements
const loadingScreen = document.getElementById('loading-screen');
const progressBar = document.getElementById('progress-bar');

// 1. Triggered when loading starts
THREE.DefaultLoadingManager.onStart = function (url, itemsLoaded, itemsTotal) {
  console.log(`Started loading: ${url}. Loaded ${itemsLoaded} of ${itemsTotal} files.`);
};

// 2. Triggered as progress updates
THREE.DefaultLoadingManager.onProgress = function (url, itemsLoaded, itemsTotal) {
  const progressRatio = itemsLoaded / itemsTotal;
  // Scale the progress bar horizontally
  progressBar.style.transform = `scaleX(${progressRatio})`;
};

// 3. Triggered when all assets are loaded
THREE.DefaultLoadingManager.onLoad = function () {
  console.log('All assets loaded successfully!');
  
  // Fade out the loading screen
  loadingScreen.classList.add('fade-out');
  
  // Optional: Remove the element from the DOM after the transition ends
  loadingScreen.addEventListener('transitionend', (event) => {
    event.target.remove();
  });
};

// 4. Triggered if any asset fails to load
THREE.DefaultLoadingManager.onError = function (url) {
  console.error(`There was an error loading: ${url}`);
};

3. Load Your Assets

Because you configured DefaultLoadingManager, any loaders instantiated afterwards will automatically report their progress to it. You do not need to manually pass the manager to your loaders.

// Set up scene, camera, and renderer
const scene = new THREE.Scene();

// Instantiate loaders - they will automatically register with DefaultLoadingManager
const textureLoader = new THREE.TextureLoader();
const gltfLoader = new GLTFLoader();

// Load textures
const texture1 = textureLoader.load('path/to/texture1.jpg');
const texture2 = textureLoader.load('path/to/texture2.jpg');

// Load 3D Models
gltfLoader.load('path/to/model.gltf', (gltf) => {
  scene.add(gltf.scene);
});