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

单页应用中如何终止GLTFLoader加载以避免移动端页面崩溃?

移动端GLTFLoader加载大文件切换页面崩溃问题解决

问题根源

快速切换页面时,未完成的GLB加载请求会持续占用网络带宽与内存资源,反复触发后移动端内存耗尽导致崩溃。单例模式仅缓存了加载完成的模型,但未处理正在进行中的加载请求中断,也没避免重复发起请求,所以问题依旧。

解决步骤

  1. 中断未完成的加载请求
    每次页面切换时,主动中断GLTFLoader的未完成请求,停止资源下载与解析,释放占用的网络和内存资源。

  2. 优化单例与加载状态管理
    全局复用同一个GLTFLoader实例,同时跟踪加载状态,避免在加载过程中重复发起请求。

  3. 完善资源清理逻辑
    不仅要释放已加载模型的材质、几何体资源,还要清理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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 06:27:47