Three.js LoadingManager的onProgress为何仅在加载完成后触发?
问题根源分析
你的LoadingManager进度跳变问题主要来自三个点:
- 默认按资源请求数统计,而非字节数:LoadingManager的
loaded和total是已完成/总资源请求的数量,不是加载的字节大小。你重复加载了platform_grass和plant_bushDetailed,浏览器缓存会让重复请求瞬间完成,直接导致进度跳变。 - 自定义
load函数未传递单资源进度回调:GLTFLoader的load方法支持单独的onProgress回调来追踪单个GLB文件的字节加载进度,但你的包装函数没利用这个,无法获取细粒度进度。 - 子资源未纳入全局进度: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]);
关键修改说明
- 字节级进度统计:放弃默认的资源请求数统计,改用实际加载的字节数,实现平滑进度。
- 避免重复计数:用
countedUrls集合记录已统计过的资源URL,防止重复加载的缓存资源重复累加总字节数。 - 传递单资源进度回调:在
load函数中给loader.load添加了xhr进度回调,追踪每个GLB文件的加载细节。 - 子资源兼容:通过LoadingManager的
onLoad钩子确保所有资源(包括GLTFLoader内部加载的子资源)完成后进度跳至100%。
内容的提问来源于stack exchange,提问作者TheHaloDeveloper
相关产品推荐
相关产品推荐

