theme: fancy
highlight: atelier-seaside-light
你还在开发传统大屏?这难道不是前端的觉醒年代?
本文使用threejs开发一款发电机拆解动画并通过交互展示零件详细信息数据交互的大屏开发,效果中规中矩,但是涵盖的知识点比较多,内容详细,也列举了众多开发中遇到的坑,内容比较长,手把手教大家写一个完整的大屏,包教包会,不收任何学费,收藏==学会。
视频讲解及源码见文末
技术栈
- three.js 0.165.0
- vite 4.3.2
- nodejs v18.19.0
效果图
加载模型
文中的模型使用的是gltf格式,在加载的时候,那就要用到threejs提供的GLTFLoader
,由于这个加载器并不是threejs内置的,所以必须使用显式引用。DRACOLoader
是处理压缩数据的,相对于那些需要解压缩的模型,如果不使用这个处理器的话,会报错,下面小结会讲到。 具体参考 # GLTF加载器
```typescript import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader, GLTF } from 'three/examples/jsm/Addons.js' // 创建解压缩器 const dracoLoader = new DRACOLoader(); // 解压缩处理的文件地址 dracoLoader.setDecoderPath(${import.meta.env.VITE_ASSETS_URL}assets/draco/gltf/
); const gltfLoader = new GLTFLoader();
// 加载gltf export function loadGltf(url: string) { gltfLoader.setDRACOLoader(dracoLoader); return new Promise ((resolve, reject) => { gltfLoader.load(url, function (gltf: GLTF) { resolve(gltf) }, function (xhr) { console.log(xhr); console.log((xhr.loaded / xhr.total * 100) + '% loaded'); }); }) } ``` loader的第二个回调是加载结束的调用,封装一个promise,即可同步加载模型,第三个回调是进度,可以从这里看到模型的一些尺寸信息和加载进度,并且可以实时展示加载进度。
模型解压缩
你可以从threejs官网提供的模型下载地址https://market.pmnd.rs/ 获取到模型,但是这些模型都是压缩后的gltf,必须要使用DRACOLoader
,如果在加载的过程中没有解压缩工具,则会报错,并且在设置地址的时候,也需要注意,dracoLoader.setDecoderPath(${import.meta.env.VITE_ASSETS_URL}assets/draco/gltf/);
我这里用的是oss地址,方便部署,而本地的node_modules的位置在node_modules\three\examples\jsm\libs\draco\gltf\draco_decoder.js
这里。
下面展示一下不加解压缩器去加载被压缩的gltf看看会报什么错,在以后大家的开发中遇到的话不至于蒙圈
加载地址是https://vazxmixjsiawhamofees.supabase.co/storage/v1/object/public/models/ruins/model.gltf
大家也可以试一下,也可以从前面提到的网站复制地址,就是网站有点慢
获取模型信息
模型加载后,可以看到一些信息,比如scene
模型场景,animations
动画列表以及其他的信息,一般用不到就不展示了,拿到模型既然要交互,那就需要对模型信息进行处理,比如获取模型尺寸,位置,世界坐标等。可以通过box3
获取,在讲解外框制作的时候会详细介绍,拿到模型后,用traverse
api遍历对象,获取每一个对象的名称,用于之后的交互,也可以在userdata属性加入自己想要的内容,但是需要注意的是 千万不要用uuid作为唯一值,因为每次加载后uuid都不同,它只是在当前加载的所有模型中是唯一的。你可以像下面代码展示的,将box3的信息添加到userdata中。
typescript alternatorGltf.scene.traverse((mesh: Object3D<Object3DEventMap>) => { if (mesh instanceof Mesh) { const boxInfo = getBox3Info(mesh); mesh.userData.boxInfo = boxInfo } })
模型与html的交互
从效果图中可以看到,右侧是所有模型信息的列表,那么我们将通过点击列表,获取模型信息,并添加外框,模型信息可以在加载模型的时候从后台获取并提前放在userdata中,这里为了展示获取模型的方法,就每次点击才获取信息。
动态添加li,mechanicalData这个数据是我提前录好的,源码中有的,数据结构是{"模型名称": '中文名称'}
,并在循环的时候,将模型名称作为数据分配到li的属性上去,方便获取,实际开发中可能需要从后台实时获取数据;
给li添加点击事件,为了不一个一个的绑定点击事件,我在UL上添加的绑定时间,并通过事件代理获取到li的数据
``ts for (let key in mechanicalData) { if (key && mechanicalData[key]) { lis +=
-
${mechanicalData[key]}
${key.indexOf('ab') === -1 ? '正常' : '异常'}
- ` } }
```
ul的事件代理
```typescript if (dom['rightMenuPart']) { dom['rightMenuPart'].addEventListener('click', (event: any) => { const name = event.target?.dataset?.model_name if (name) { const model = scene.getObjectByName(name); if (model) { createBox(model) } }
})
}
``
上面的代码就是根据获得绑定在li上的
model_name信息,获取当前被点击的模型名称,并通过[# .getObjectByName](https://threejs.org/docs/index.html#api/zh/core/Object3D.getObjectByName)获取到场景中的模型,这个方法只返回第一个获取到的值,所以尽量保持场景中的模型唯一,或者如果有分组,可以用
group.getObjectByName`,但也要确保组内的模型名称唯一,这里多提一下,除了通过名称获取,还可以通过自定义属性 # .getObjectByProperty获取模型。动画
关于动画的详细内容可以参考之前的文章 # three.js——镜头跟踪,下面讲的是作为这篇的补充。
剪辑动画
先给大家看一下裁剪之前的动画效果,
完整的动画是从收起状态到展开状态再到收起,那么我们就可以将动画裁剪成2部分,第一部分是展开,观察效果图可以看出来,完整展开的时间大约是2s,第二部分是收起,动画的最开始就是收起状态,所以我们只裁剪0.1s的位置就可以,先上代码,一会解释
```typescript // 定义一个动画器 export let motorAnimation: HandleAnimation
// 将模型加载到动画器中 motorAnimation = new HandleAnimation(alternatorGltf.scene, alternatorGltf.animations)
// 裁剪动画并命名为expand motorAnimation.clipAnimation('Take 001', 2, 'expand') // 裁剪