Skip to content

Three.js Loading Manager — Asset Loading and Progress Tracking

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Three.js Loading Manager. We cover key concepts, practical examples, and best practices to help you master this topic.

Three.js LoadingManager provides centralized asset loading control with progress tracking, error handling, and start/complete callbacks for managing complex loading pipelines.

What You'll Learn

By the end of this guide, you will use LoadingManager to track loading progress, show a loading screen, handle errors for missing assets, preload multiple assets with dependencies, and integrate with GLTFLoader.

Why Loading Management Matters

Without a loading manager, assets load asynchronously with no feedback. In Durga Antivirus Pro, the threat dashboard shows a loading progress bar while models and textures download. Without this, users stare at a blank screen wondering if the app is broken.

flowchart LR
    A[Page Load] --> B[Show Loading Screen]
    B --> C[Create LoadingManager]
    C --> D[Queue Assets]
    D --> E[Loading Manager Tracks Progress]
    E --> F[Update Progress Bar]
    F --> G{All Loaded?}
    G -->|No| E
    G -->|Yes| H[Hide Loading Screen]
    H --> I[Start Application]

Basic LoadingManager

var manager = new THREE.LoadingManager();

manager.onStart = function(url, itemsLoaded, itemsTotal) {
    console.log('Started loading: ' + url);
};

manager.onProgress = function(url, itemsLoaded, itemsTotal) {
    var progress = (itemsLoaded / itemsTotal) * 100;
    console.log('Loading: ' + progress.toFixed(1) + '%');
};

manager.onLoad = function() {
    console.log('All assets loaded!');
};

manager.onError = function(url) {
    console.error('Error loading: ' + url);
};

var textureLoader = new THREE.TextureLoader(manager);
var cubeTextureLoader = new THREE.CubeTextureLoader(manager);
var gltfLoader = new GLTFLoader(manager);

Expected output: Console logs showing each asset as it loads, overall progress percentage, and a completion message when all assets finish.

Loading Screen With Progress Bar

var progressBar = document.getElementById('progress-bar');
var loadingScreen = document.getElementById('loading-screen');

var manager = new THREE.LoadingManager();

manager.onProgress = function(url, loaded, total) {
    var percent = (loaded / total) * 100;
    progressBar.style.width = percent + '%';
};

manager.onLoad = function() {
    loadingScreen.style.display = 'none';
};

manager.onError = function(url) {
    console.error('Failed to load: ' + url);
    // Optionally show error on screen
};

Expected output: A loading screen with a progress bar that fills from 0% to 100% as each asset finishes loading.

Loading Multiple Asset Types

var manager = new THREE.LoadingManager();
var textureLoader = new THREE.TextureLoader(manager);
var gltfLoader = new GLTFLoader(manager);
var audioLoader = new THREE.AudioLoader(manager);

// Queue all assets
textureLoader.load('textures/diffuse.jpg');
textureLoader.load('textures/normal.jpg');
gltfLoader.load('models/character.glb');
gltfLoader.load('models/environment.glb');
audioLoader.load('audio/background.mp3');

Expected output: The manager tracks all 5 assets across different loaders as a single queue. Progress reflects total items.

Error Recovery

manager.onError = function(url) {
    var retryButton = document.createElement('button');
    retryButton.textContent = 'Retry: ' + url;
    retryButton.onclick = function() {
        textureLoader.load(url);
    };
    document.getElementById('error-area').appendChild(retryButton);
};

Expected output: When an asset fails to load, a retry button appears. Clicking it re-queues the failed asset.

Common Mistakes

1. Creating Separate Managers for Each Loader

Each manager tracks its own queue. Use a single manager shared across all loaders for unified progress tracking.

2. Not Handling onError

Users see a broken scene with no feedback. Always implement onError to show a fallback or retry option.

3. Zero Division on Progress

When itemsTotal is 0, the progress calculation divides by zero. Check itemsTotal > 0 before computing percentage.

4. Forgetting to Remove Loading Screen

If onLoad never fires (e.g., an asset silently fails), the loading screen persists forever. Add a timeout fallback.

5. Loading the Same Asset Multiple Times

The manager counts each load call separately. Cache loaded assets to avoid redundant downloads.

Practice Questions

Q1: What events does LoadingManager emit? A: onStart (when loading begins), onProgress (per-file progress), onLoad (all complete), onError (per-file error).

Q2: How do you share a LoadingManager across loaders? A: Pass the manager instance as the first argument to each loader constructor: new THREE.TextureLoader(manager).

Q3: What happens when an asset fails to load? A: The onError callback fires with the failed URL. The manager continues loading remaining assets. onLoad fires when all non-failed assets complete.

Q4: How do you show a loading progress bar? A: In onProgress, update the progress bar width as (loaded / total) * 100%.

Q5: How do you handle the case where onLoad never fires? A: Add a setTimeout fallback that hides the loading screen after a maximum timeout (e.g., 30 seconds).

Challenge: Build a scene with 10 different colored textures applied to spheres. Show a loading screen with a progress bar that fills smoothly from 0% to 100% as each texture loads. Add a retry button for failed textures.

FAQ

Can LoadingManager handle nested asset dependencies?

No. The manager tracks flat load calls. For nested dependencies (e.g., glTF referencing external textures), you need custom tracking or a preloading step.

Does the manager cache loaded assets?

No. The manager only tracks loading state. Asset caching is handled by the individual loaders or browser HTTP cache.

What happens if I load 0 assets?

itemsTotal is 0. onStart and onLoad fire immediately. Always start loading at least one asset or handle the zero case.

Can I use multiple managers?

Yes. Use separate managers for different loading phases (e.g., critical assets first, optional assets later).

How do I track loading of Draco-compressed models?

Same as any GLTFLoader with manager. The Draco decoder loads separately via its own loader, which does not report through the manager.

Try It Yourself — Loading Screen Demo

Build a page that simulates loading multiple assets with a visible progress bar.

<!DOCTYPE html>
<html>
<head>
    <title>Loading Manager Demo</title>
    <style>
        body { margin: 0; overflow: hidden; font-family: sans-serif; }
        #loading-screen { position: fixed; inset: 0; background: #0a0a1a; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 100; color: white; }
        #progress-container { width: 300px; height: 20px; background: #222; border-radius: 10px; overflow: hidden; margin: 20px; }
        #progress-bar { height: 100%; width: 0; background: linear-gradient(90deg, #4ecdc4, #44aa88); transition: width 0.2s; }
        #loading-text { font-size: 14px; color: #888; }
        #scene-container { width: 100%; height: 100%; }
        #error-container { margin-top: 10px; }
        .retry-btn { margin: 4px; padding: 4px 8px; cursor: pointer; background: #333; color: white; border: 1px solid #555; border-radius: 4px; font-size: 12px; }
    </style>
</head>
<body>
<div id="loading-screen">
    <h2>Loading Scene</h2>
    <div id="progress-container"><div id="progress-bar"></div></div>
    <div id="loading-text">0%</div>
    <div id="error-container"></div>
</div>
<script type="importmap">
{
    "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
    }
}
</script>
<script type="module">
import * as THREE from "three";

var progressBar = document.getElementById('progress-bar');
var loadingText = document.getElementById('loading-text');
var loadingScreen = document.getElementById('loading-screen');
var errorContainer = document.getElementById('error-container');

var manager = new THREE.LoadingManager();

manager.onProgress = function(url, loaded, total) {
    var pct = Math.round((loaded / total) * 100);
    progressBar.style.width = pct + '%';
    loadingText.textContent = pct + '%';
};

manager.onLoad = function() {
    loadingScreen.style.display = 'none';
    initScene();
};

manager.onError = function(url) {
    var btn = document.createElement('button');
    btn.className = 'retry-btn';
    btn.textContent = 'Retry: ' + url.split('/').pop();
    btn.onclick = function() {
        btn.remove();
        new THREE.TextureLoader(manager).load(url);
    };
    errorContainer.appendChild(btn);
};

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);

function initScene() {
    document.body.appendChild(renderer.domElement);

    var textureLoader = new THREE.TextureLoader(manager);
    var urls = [
        'https://threejs.org/examples/textures/uv_grid_opengl.jpg',
        'https://threejs.org/examples/textures/planets/earth_atmos_2048.jpg',
        'https://threejs.org/examples/textures/colors.png'
    ];

    urls.forEach(function(url) {
        textureLoader.load(url);
    });
}

// Start initial empty scene
initScene();

function animate() {
    renderer.render(scene, camera);
    requestAnimationFrame(animate);
}
animate();

window.addEventListener("resize", function() {
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>

What's Next

Create billboard sprites that always face the camera.

Sprites — Billboard sprites and sprite materials. Lines — Drawing lines and polylines in Three.js.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro