本文介绍如何在 Three.js 中利用 IndexedDB 缓存 GLB 模型以提升加载性能。针对下载过程中常见的 206 错误及 axios 的 blob 响应问题,建议改用 fetch 方式,并根据文件类型(如 json 或 gbk 编码)灵活使用 json() 或 arraybuffer() 方法。最后通过 URL.createObjectURL 将 Blob 转换为 Three.js 可加载的 URL,实现模型的高效复用。
探讨如何在 Three.js 项目中利用 IndexedDB 实现 GLB 模型的本地缓存与加载。
在实际开发中,文件下载过程偶尔会出现 206 错误,这是一个需要警惕的问题。
当使用 axios 进行下载时,设置 responseType 为 'blob' 并不保证每次都能成功获取文件。
下载失败的原因通常与文件的格式及其编码方式有关。
后续尝试改用 fetch 方式下载,但需注意 fetch 的 blob() 方法并非对所有文件都适用。例如,json 文件应使用 json() 解析,而对于编码为 gbk 格式的文件,使用 Blob() 可能失败,此时应改用 arraybuffer() 处理。
1、加载工具类 indexeddb.js
具体实现中,我们需要封装一个基于 IndexedDB 的工具类来处理数据的存储与读取。
const DB_NAME = 'daxue1';
const DB_VERSION = 1; // Use a long long for this value (don't use a float)
const DB_STORE_NAME = 'model_glb';
export class DBUtil {
async get(url, onProgress) {
this.db = await this.initDataBase();
let getRequest = this.db
.transaction([DB_STORE_NAME], "readwrite").objectStore(DB_STORE_NAME).get(url);
let that = this;
return new Promise((resolve, reject) => {
getRequest.onsuccess = function (event) {
let modelFile = event.target.result;
// 假如已经有缓存了 直接用缓存
if (modelFile) {
if (onProgress) {
onProgress(100);
}
resolve(modelFile.blob)
} else {
// 假如没有缓存 请求新的模型存入
that.put(url, onProgress).then((blob) => {
resolve(blob)
}).catch(() => {
reject()
});
}
};
getRequest.onerror = function (event) {
console.log('error', event)
reject()
}
})
}
async put(url, onProgress) {
const response = await fetch(url);
if (response.status !== 200) {
throw new Error('Request failed');
}
const contentLength = response.headers.get('Content-Length');
console.log(contentLength)
const totalBytes = parseInt(contentLength, 10);
let downloadedBytes = 0;
const readableStream = response.body;
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
const reader = readableStream.getReader();
const pump = async () => {
const { done, value } = await reader.read();
if (done) {
writer.close();
return;
}
writer.write(value);
downloadedBytes += value.length;
if (onProgress) {
const progress = (downloadedBytes / totalBytes) * 100;
console.log(progress.toFixed(2))
onProgress(progress.toFixed(2));
}
return pump();
};
await pump();
let blob = null;
try {
blob = await new Response(readable).arrayBuffer();
} catch (e) {
console.log('请求arrayBuffer失败,用blob方式')
blob = await new Response(readable).blob();
}
let obj = {
ssn: url
}
obj.blob = new Blob([blob])
const inputRequest = this.db
.transaction([DB_STORE_NAME], "readwrite")
.objectStore(DB_STORE_NAME)
.add(obj);
return new Promise((resolve, reject) => {
inputRequest.onsuccess = function() {
console.log('glb数据添加成功');
resolve(obj.blob);
};
inputRequest.onerror = function(evt) {
console.log('glb数据添加失败', evt);
reject();
};
});
}
initDataBase() {
if (!window.indexedDB) {
console.log("Your browser doesn't support a stable version of IndexedDB.")
return;
}
let request = indexedDB.open(DB_NAME, DB_VERSION);
return new Promise((resolve, reject) => {
request.onerror = function () {
console.log("error: create db error");
reject()
};
request.onupgradeneeded = function (evt) {
evt.currentTarget.result.createObjectStore(
DB_STORE_NAME, {keyPath: 'ssn'});
};
request.onsuccess = function (evt) {
console.log("onsuccess: create db success ");
resolve(evt.target.result)
};
})
}
}


2、加载模型
let url = URL.createObjectURL(new Blob([blob])) 核心步骤是利用 URL.createObjectURL 方法,将获取到的 Blob 对象转换为 Three.js 加载器可以直接识别的 URL 地址。
import * as THREE from 'three/build/three.module.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import {DBUtil} from "../util/DBUtil";
let model = new THREE.Group();
new DBUtil().get('/droc_model/changsharelease.glb',(progress) => {
console.log(progress)
}).then((blob) => {
console.log('getModel获取成功', blob);
let loader = new GLTFLoader();
let url = URL.createObjectURL(new Blob([blob]));
loader.load(url, function (gltf) {
model.add(gltf.scene);
}, function ( xhr ) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
})
})
export {
model
}总结
以上内容为个人实践经验总结,旨在提供参考,欢迎读者交流指正。