You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js LoadingManager的onProgress为何仅在加载完成后触发?

问题根源分析

你的LoadingManager进度跳变问题主要来自三个点:

  1. 默认按资源请求数统计,而非字节数:LoadingManager的loaded和total是已完成/总资源请求的数量,不是加载的字节大小。你重复加载了platform_grass和plant_bushDetailed,浏览器缓存会让重复请求瞬间完成,直接导致进度跳变。
  2. 自定义load函数未传递单资源进度回调:GLTFLoader的load方法支持单独的onProgress回调来追踪单个GLB文件的字节加载进度,但你的包装函数没利用这个,无法获取细粒度进度。
  3. 子资源未纳入全局进度:GLTFLoader加载模型时会内部加载纹理、材质等子资源,这些默认用GLTFLoader的内部LoadingManager,不会触发你自定义的manager的onProgress,导致最后剩下的2%进度迟迟不更新。

修复方案(字节级平滑进度)

以下是能实现平滑进度条的完整代码:

// 全局进度统计:总字节数、已加载字节数
let totalBytes = 0;
let loadedBytes = 0;
// 记录已统计过的资源URL,避免重复计数
const countedUrls = new Set();
// 缓存单个资源的已加载字节数,避免重复累加
const resourceLoadedCache = {};

const loadingManager = new THREE.LoadingManager();

// 资源开始加载时,提前获取总字节数
loadingManager.onStart = function(url) {
  if (!countedUrls.has(url)) {
    countedUrls.add(url);
    // 用HEAD请求获取资源总大小,不下载文件
    fetch(url, { method: 'HEAD' })
      .then(res => {
        const contentLength = parseInt(res.headers.get('Content-Length'), 10);
        if (!isNaN(contentLength)) {
          totalBytes += contentLength;
          updateProgress();
        }
      })
      .catch(err => console.error('获取资源大小失败', err));
  }
};

// 所有资源加载完成后强制设为100%
loadingManager.onLoad = function() {
  updateProgress(100);
};

// 更新DOM进度的通用函数
function updateProgress(forcePercent) {
  const percent = forcePercent || Math.min(Math.round((loadedBytes / totalBytes) * 100), 99);
  document.getElementById('loading-progress').value = percent;
  document.getElementById('loading-progress-label').textContent = `${percent}%`;
}

// 单个资源的字节进度回调
function trackResourceProgress(url, xhr) {
  const prevLoaded = resourceLoadedCache[url] || 0;
  loadedBytes += xhr.loaded - prevLoaded;
  resourceLoadedCache[url] = xhr.loaded;
  updateProgress();
}

const loader = new THREE.GLTFLoader(loadingManager);

function load(asset, pos=[0, 0, 0], rot=[-Math.PI / 2, Math.PI, Math.PI], scale=[5, 5, 5], appendTo){
  const url = `Assets/${asset}.glb`;
  loader.load(
    url,
    function(object){
      object.scene.position.set(pos[0], pos[1], pos[2]);
      object.scene.rotation.set(rot[0], rot[1], rot[2]);
      object.scene.scale.set(scale[0], scale[1], scale[2]);
      object.scene.name = asset;

      scene.add(object.scene);

      if(appendTo != undefined){
        appendTo.push(object.scene);
      }
    },
    // 传递单资源的进度回调
    (xhr) => trackResourceProgress(url, xhr),
    // 错误回调,方便排查问题
    (err) => console.error(`加载${asset}失败`, err)
  );
};

// 加载资源
load('platform_grass', [-5, 4, -0.5]);
load('platform_grass', [-2, 3, -0.5], undefined, [5, 10, 5]);
load('rock_largeA', [1, 3, -1], undefined, [2, 2, 2]);
load('plant_bushDetailed', [-5, 1, -1], undefined, [3, 3, 3]);
load('plant_bushDetailed', [-2, 6, -1], undefined, [3, 3, 3]);

关键修改说明
  1. 字节级进度统计:放弃默认的资源请求数统计,改用实际加载的字节数,实现平滑进度。
  2. 避免重复计数:用countedUrls集合记录已统计过的资源URL,防止重复加载的缓存资源重复累加总字节数。
  3. 传递单资源进度回调:在load函数中给loader.load添加了xhr进度回调,追踪每个GLB文件的加载细节。
  4. 子资源兼容:通过LoadingManager的onLoad钩子确保所有资源(包括GLTFLoader内部加载的子资源)完成后进度跳至100%。

内容的提问来源于stack exchange,提问作者TheHaloDeveloper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:37:49