案例展示
模型本地缓存:基于 IndexedDB 实现,大幅提升模型加载速度。
查看位置:浏览器开发者工具 → Application → Storage → IndexedDB
模型格式
支持 gltf、glb、fbx、obj、stl、json、zip、text、assemble 等格式。
模型渲染总时长对比
已缓存:未测试
未缓存:未测试
<template>
<div class="vp-px2vw">
<div class="container">
<div id="TO"></div>
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner"></div>
<span class="loading-text">{{ loadingText }}</span>
</div>
<div id="formContainer">
<ModelRenderTimePanel
:cached-time="cachedTime"
:uncached-time="uncachedTime"
@loadCachedModel="loadCachedModel"
@loadUncachedModel="loadUncachedModel" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import ModelRenderTimePanel from './ModelRenderTimePanel.vue'
const loading = ref(false)
const cachedTime = ref(null)
const uncachedTime = ref(null)
let TO = null,
modelDataList = []
const loadingText = ref('加载中...')
let modelList = [
{
modelType: 'gltf',
modelName: 'ABB机器人-1',
id: 3012,
position: {
x: 0,
y: 0,
z: 0
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/56201403'
}
},
{
modelType: 'gltf',
modelName: '非皮带传送带-1',
id: 3021,
position: {
x: -24.90687943165758,
y: 0,
z: 16.922502148523417
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/258642254'
}
},
{
modelType: 'gltf',
modelName: '精雕机床-1',
id: 3016,
position: {
x: -6,
y: 0,
z: -23
},
rotation: {
x: 0,
y: 3.14,
z: 0
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/701730674'
}
},
{
modelType: 'gltf',
modelName: '数字机床-1',
id: 3014,
position: {
x: 46,
y: 0,
z: -4
},
rotation: {
x: 3.14,
y: 0,
z: 3.14
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/878966042'
}
}
]
onMounted(async () => {
initScene()
})
function initScene() {
//初始化场景
TO = new ThingOrigin('fileModel', document.getElementById('TO'), {
camera: {
lookAt: {
x: 25.62454680418967,
y: -2.3106037456621856,
z: -66.99965038950995
},
position: {
x: 41.87976418982612,
y: 78.50564268979477,
z: 117.31701183506453
}
}
})
// 首次先加载未缓存的模型
loadUncachedModel()
}
// 清除所有模型
function clearAllModels() {
modelDataList.forEach((model) => {
TO.scene.removeModelByName(model.name)
})
modelDataList = []
}
// 设置模型的位置和旋转
function setupModelTransform(model, modelInfo) {
if (modelInfo.position) {
model.position.set(modelInfo.position.x, modelInfo.position.y, modelInfo.position.z)
}
if (modelInfo.rotation) {
model.rotation.set(modelInfo.rotation.x, modelInfo.rotation.y, modelInfo.rotation.z)
}
}
// 添加模型到场景
function addModelToScene(model, modelInfo) {
setupModelTransform(model, modelInfo)
modelDataList.push(model)
TO.scene.add(model)
}
// 加载单个模型(已缓存)
async function loadSingleCachedModel(modelInfo) {
const accessModel = await TO.indexedDB.accessModel(modelInfo)
if (accessModel.info.saved) {
const modelUrl = accessModel.modelUrl
return TO.model.loadModel(accessModel.info, modelUrl).then((model) => {
addModelToScene(model, modelInfo)
})
}
}
// 加载单个模型(未缓存)
async function loadSingleUncachedModel(modelInfo) {
try {
const insertModel = await TO.indexedDB.insertModel(modelInfo)
if (insertModel && insertModel.info && insertModel.info.saved) {
const modelUrl = insertModel.modelUrl
return TO.model
.loadModel(insertModel.info, modelUrl)
.then((model) => {
addModelToScene(model, modelInfo)
})
.catch((error) => {
console.error(`加载模型 ${modelInfo.modelName} 失败:`, error)
// 返回 resolved Promise,确保不影响后续代码
return Promise.resolve()
})
} else {
// 写入失败,自动跳过
console.warn(`模型 ${modelInfo.modelName} 写入失败,已跳过`)
return Promise.resolve()
}
} catch (error) {
// 捕获所有可能的错误,确保不影响后续代码
console.error(`加载模型 ${modelInfo.modelName} 时发生错误:`, error)
return Promise.resolve()
}
}
// 加载已缓存的模型
async function loadCachedModel() {
loadingText.value = '二次加载:从本地 IndexedDB 读取(无网络请求)'
loading.value = true
clearAllModels()
const totalStartTime = performance.now()
const loadPromises = modelList.map((modelInfo) => loadSingleCachedModel(modelInfo))
await Promise.all(loadPromises)
const totalEndTime = performance.now()
cachedTime.value = Math.round(totalEndTime - totalStartTime)
loading.value = false
}
// 强制加载未缓存模型(先删除缓存再加载)
async function loadUncachedModel() {
loadingText.value = '首次加载:从网络请求并缓存到本地 IndexedDB'
loading.value = true
deleteCachedModel()
const totalStartTime = performance.now()
const loadPromises = modelList.map((modelInfo) => loadSingleUncachedModel(modelInfo))
await Promise.all(loadPromises)
const totalEndTime = performance.now()
uncachedTime.value = Math.round(totalEndTime - totalStartTime)
loading.value = false
}
async function deleteCachedModel() {
clearAllModels()
// cachedTime.value = null;
// uncachedTime.value = null;
// 删除所有模型的缓存
const deletePromises = modelList.map(async (modelInfo) => {
try {
await TO.indexedDB.deleteModel(modelInfo.id)
} catch (e) {
// 忽略删除错误(可能不存在)
}
})
await Promise.all(deletePromises)
}
</script>
<style scoped lang="scss">
.vp-px2vw {
.container {
position: relative;
}
#TO {
width: 100%;
height: 400px;
position: relative;
}
#formContainer {
position: absolute;
top: 10px;
right: 10px;
font-size: 14px;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
z-index: 10;
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.loading-text {
margin-top: 12px;
color: #fff;
font-size: 14px;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>API 介绍
indexedDB.accessModel
| 方法签名 | 返回值 | 描述 |
|---|---|---|
accessModel(modelInfo: modelInfoParams) | PromiseLike<accessResult> | 判断是否已缓存此模型,返回是否存储,若已缓存返回模型参数 |
indexedDB.insertModel
| 方法签名 | 返回值 | 描述 |
|---|---|---|
insertModel(modelInfo: modelInfoParams) | Promise<modelInfoParams> | 向indexedDB数据表中,存储模型 |
参数说明:
| 参数名 | 说明 | 类型 | 必填 | 默认值 |
|---|---|---|---|---|
modelInfo | 模型参数 | modelInfoParams | 是 |
indexedDB.deleteModel
| 方法签名 | 返回值 | 描述 |
|---|---|---|
deleteModel(id: number) | Promise<boolean> | indexedDB数据表中,删除模型 |
参数说明:
| 参数名 | 说明 | 类型 | 必填 | 默认值 |
|---|---|---|---|---|
id | 模型 id | number | 是 |