单页应用中如何终止GLTFLoader加载以避免移动端页面崩溃?
移动端GLTFLoader加载大文件切换页面崩溃问题解决
问题根源
快速切换页面时,未完成的GLB加载请求会持续占用网络带宽与内存资源,反复触发后移动端内存耗尽导致崩溃。单例模式仅缓存了加载完成的模型,但未处理正在进行中的加载请求中断,也没避免重复发起请求,所以问题依旧。
解决步骤
中断未完成的加载请求
每次页面切换时,主动中断GLTFLoader的未完成请求,停止资源下载与解析,释放占用的网络和内存资源。优化单例与加载状态管理
全局复用同一个GLTFLoader实例,同时跟踪加载状态,避免在加载过程中重复发起请求。完善资源清理逻辑
不仅要释放已加载模型的材质、几何体资源,还要清理loader、loadingManager等引用,避免内存泄漏。
优化后的代码示例
class Character extends EventEmitter { loadingManager = null loader = null model = null clock = new THREE.Clock() isLoading = false loadPromise = null // 缓存加载Promise,避免重复请求 loaderHandler = null // 保存加载请求句柄,用于中断 init() { // 如果已加载完成,直接返回成功Promise if (this.model) { return Promise.resolve() } // 如果正在加载,返回同一个Promise if (this.isLoading) { return this.loadPromise } this.isLoading = true this.loadPromise = new Promise((resolve, reject) => { this.loadingManager = new THREE.LoadingManager() this.loadingManager.onError = error => { this.isLoading = false this.loadPromise = null reject(error) } this.loader = new GLTFLoader(this.loadingManager) // 保存请求句柄,用于后续中断 this.loaderHandler = this.loader.load( `${import.meta.env.VITE_FILE_PATH}/character.glb`, gltf => { this.model = gltf.scene this.model.traverse(o => { if (o.isMesh) { o.castShadow = true o.receiveShadow = true } }) this.isLoading = false resolve() }, ({ loaded, total }) => { this.emit('progress', loaded / total) }, error => { this.isLoading = false this.loadPromise = null reject(error) } ) }) return this.loadPromise } unload() { // 中断未完成的加载请求 if (this.loaderHandler?.abort) { this.loaderHandler.abort() } // 释放已加载的模型资源 this.model?.traverse(obj => { if (obj.material) { obj.material.dispose() // 遍历材质的所有可释放属性,简化代码 Object.values(obj.material).forEach(val => { if (val?.dispose) val.dispose() }) } if (obj.geometry) { obj.geometry.dispose() } }) // 清理所有引用,帮助垃圾回收 this.model = null this.loader = null this.loadingManager = null this.loaderHandler = null this.isLoading = false this.loadPromise = null return this } }
关键修改说明
- 加载状态跟踪:通过
isLoading和loadPromise确保同一时间只有一个加载请求,避免重复触发。 - 请求中断:保存loader返回的请求句柄,在
unload时调用abort()中断未完成的下载。 - 资源清理优化:遍历材质的所有属性进行释放,同时清理loader、loadingManager等实例引用,减少内存占用。
额外注意事项
- 在页面/组件卸载的生命周期钩子中(比如Vue的
onUnmounted、React的useEffect清理函数),务必调用character.unload()。 - 24MB的GLB文件建议做压缩优化(比如使用glTF-Pipeline压缩纹理、简化模型面数),从根源减少加载压力。
内容的提问来源于stack exchange,提问作者user24173533
相关产品推荐
相关产品推荐

