3、加载管理器基础:Three.js LoadingManager介绍、事件监听(onStart, onProgress, onLoad, onError)

好,咱们今天聊聊加载管理器。说实话,很多新手做 Three.js 项目时,最容易忽略的就是加载状态的管理。你想想看,一个 3D 场景里动不动就是几 MB 甚至几十 MB 的模型、贴图,用户点开页面后干等着,连个进度条都没有——这体验能好吗?

我个人习惯是,只要项目里涉及外部资源加载,第一件事就是先把 LoadingManager 安排上。它就像个监工,帮你盯着每一个资源的加载状态。

3.1 什么是 LoadingManager?

LoadingManager 是 Three.js 提供的一个加载管理器。它本身不负责加载资源,而是用来统一管理多个加载器的行为。说白了,它就是给 Loader 们配了一个「总指挥」。

默认情况下,每个 Loader(比如 TextureLoader、GLTFLoader)都有自己的内部管理器。但如果你想让它们共享同一个进度回调、同一个错误处理逻辑,那就得自己创建一个 LoadingManager 实例。

核心作用:

  • 统一监听所有加载器的开始、进度、完成、错误事件
  • 支持设置加载基路径(base URL)
  • 支持跨加载器共享资源缓存

3.2 创建 LoadingManager

创建方式非常简单,直接 new 一个就行:

const manager = new THREE.LoadingManager();

然后把这个 manager 传给各个 Loader:

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

嗯,这里要注意:不是所有 Loader 都支持传入 manager。Three.js 官方提供的 Loader 基本都支持,但第三方 Loader 就不一定了。我曾经在项目里遇到过一个第三方模型格式的 Loader,死活不认 manager,最后只能自己手动封装了一层。

3.3 四个核心事件

LoadingManager 提供了四个关键事件回调。我把它们整理成了表格,方便你对照:

事件 回调参数 触发时机
onStart (url, itemsLoaded, itemsTotal) 每个资源开始加载时触发
onProgress (url, itemsLoaded, itemsTotal) 每个资源加载进度更新时触发
onLoad () 所有资源加载完成时触发
onError (url) 某个资源加载失败时触发

3.4 实战:写一个完整的加载管理器

光说不练假把式。咱们直接写一个完整的例子:

const manager = new THREE.LoadingManager();

// 开始加载
manager.onStart = function(url, itemsLoaded, itemsTotal) {
  console.log(`开始加载: ${url}`);
  console.log(`已加载 ${itemsLoaded}/${itemsTotal}`);
  // 这里可以显示一个加载动画
};

// 加载进度
manager.onProgress = function(url, itemsLoaded, itemsTotal) {
  const progress = (itemsLoaded / itemsTotal) * 100;
  console.log(`加载进度: ${progress.toFixed(1)}%`);
  // 更新进度条
  document.getElementById('progress-bar').style.width = progress + '%';
};

// 全部完成
manager.onLoad = function() {
  console.log('所有资源加载完成!');
  // 隐藏加载界面,显示场景
  document.getElementById('loading-screen').style.display = 'none';
};

// 加载出错
manager.onError = function(url) {
  console.error(`加载失败: ${url}`);
  // 显示错误提示
  document.getElementById('error-message').textContent = 
    `资源加载失败: ${url}`;
};

// 使用这个 manager 创建加载器
const loader = new THREE.TextureLoader(manager);
loader.load('textures/wood.jpg');
loader.load('textures/metal.jpg');
loader.load('textures/stone.jpg');

小技巧:onProgress 里的 itemsLoaded 和 itemsTotal 是按文件个数计算的,不是按字节。如果你需要精确的字节级进度,得自己用 XMLHttpRequest 去封装。

3.5 避坑指南

我曾经在一个电商项目里踩过一个坑:同时加载了 20 多个纹理和 3 个 GLTF 模型,结果 onLoad 事件一直不触发。排查了半天才发现,有个纹理的 URL 写错了,但 onError 也没被调用——因为那个 Loader 压根没绑定 manager。

所以这里给你三个建议:

  1. 统一管理:项目中所有 Loader 尽量使用同一个 manager 实例
  2. 错误处理要冗余:除了 onError,最好再加个全局的 try-catch
  3. 测试边界情况:比如网络断开、资源 404、跨域问题,这些都要提前测

注意:LoadingManager 的 onLoad 事件只有在所有通过该 manager 创建的 Loader 都完成加载后才会触发。如果你在 onLoad 之后又调用了 loader.load(),那 onLoad 不会再次触发。需要重新创建 manager 或者手动重置状态。

3.6 进阶:设置基路径

LoadingManager 还有一个很实用的功能——设置基路径。当你的资源都放在同一个目录下时,这个功能能省不少事:

const manager = new THREE.LoadingManager();
manager.setURLModifier(function(url) {
  return '/assets/' + url;
});

// 现在只需要传文件名
const loader = new THREE.TextureLoader(manager);
loader.load('wood.jpg');  // 实际加载 /assets/wood.jpg
loader.load('metal.jpg'); // 实际加载 /assets/metal.jpg

这个 setURLModifier 其实是个万能钩子。你可以在里面做 URL 重写、添加版本号、甚至做资源映射。我个人习惯用它来做 CDN 路径切换——开发环境用本地路径,生产环境自动替换成 CDN 地址。

3.7 小结

LoadingManager 是 Three.js 资源管理的基石。它不复杂,但很实用。记住四个核心事件:onStart、onProgress、onLoad、onError,再配合 setURLModifier,基本上能覆盖 90% 的加载管理需求。

下一章咱们会深入讲 TextureLoader 和纹理加载的细节。到时候你会发现,有了 LoadingManager 的基础,理解纹理加载的进度控制会轻松很多。

一句话总结:LoadingManager 就是加载器的「总指挥」,让多个加载器步调一致,让你能精确掌控加载流程的每一个环节。