最近在开发Threejs的时候发现个现象,就是编译的次数太频繁,这个体现在比如修改一个属性或者变量的时候,为了看到具体效果,就得command+s一下,有人可能会说这个不是很正常吗,做web开发编译项目不是很常规的操作吗?不过这个操作得看用在什么地方,如果是加了一段函数,或者是一个组件,编译一下是没有问题的,但是如果像是在调界面,调样式,修改的地方仅仅局限在某个变量,属性的时候,编译一下虽说也没问题,但就是会浪费计算机资源,尤其是在一些大型的Threejs项目上,这种浪费现象就变得更加明显,那么有没有一种办法可以改善一下这种现象呢,能够改善后达到以下效果
- 调试Threejs组件的时候减少编译次数
- 变量,属性被更改时可以零编译地观察到具体效果
Storybook简介
Storybook这个库相信有很多小伙伴已经用过了,也可能有小伙伴没有听说过这个库,有人用它来生成组件的文档,也有人用它作为ui组件的测试工具,官方给出的介绍也差不多
左边的大标题意思是无需费力的构建ui,右边小字部分我划横线的地方就明确指出Storybook的职能范围,ui开发,测试以及文档化,而对于我们开发的Threejs里面的模型来讲,它其实就是一个React组件,我们就可以利用Storybook,以测试ui的方式来调整我们的组件样式,直到我们得到想要的样式为止
创建项目
先使用CRA
命令来创建个项目,执行以下命令
lua
npx create-react-app --template typescript three-story
等待执行完毕后,cd到three-story目录下,再执行以下命令,安装storybook
kotlin
npx storybook@latest init
一路yes之后,浏览器里面就会自动开启Storybook的页面
Storybook刚初始化完成会有一个使用指导,可以直接跳过,也可以看下,里面一步步介绍了文档里面每个部分都有什么作用以及如何写一个story,下面来看下项目,我们打开刚才创建的three-story
,主要关注src/stories目录底下的文件,会发现里面的文件刚好与文档里面Example
目录底下的文件名一一对应
这样就很明显,除了.css
样式文件之外,每一个组件文档都是由一个.tsx
文件和一个.stories.ts
文件组成,.tsx
文件就是我们的组件了,以Button
为例我们看下Button.tsx
里面都写了啥
有一些声明的组件入参以及组件的渲染逻辑,跟我们正常的一个React组件没啥区别,而我们可以注意到,这里声明的这些参数,刚好体现在文档里面该组件底下的控制面板中,包括连注释,默认值都写在了里面
最后一列Control
相当于就是给这个属性赋值了,赋值的时候所使用的组件类型,我们就要去Button.stories.ts
文件里面看了
首先能够看到里面定义了一个元数据meta
,这个元数据里面就包含着该组件所有story
的属性,这些属性从上到下的作用分别是
- title:该文档位于的目录以及文档的名字
- component:当前Story作用在哪个组件上
- parameters:组件预览的位置
- tags:当前组件是否自动生成文档
- argTypes:用来操作某个参数
这里argType
可能有些小伙伴还不理解究竟是干啥用的,我们这里稍微改下backgroundColor
的值,现在control
的值是color
,我们改成date
试试
然后发现,更改后在控制面板中,原本可以给属性backgroundColor
选择颜色的地方,现在居然不能选了,变成了选择日期了
这个就是argTypes
的作用,可以更改操作属性值的组件类型,既然这样我们换个属性试试,backgroundColor
给它恢复到color
,在控制面板的属性中我们还看到size
属性用的组件刚好是radio
,这是在有限的值之中选择一个,那么我们可以给它改成下拉框类型,代码如下
将size
的操作类型改成select
之后,我们看到控制面板中属性size
的位置就已经从radio
组件变成了select
组件
这个就是argTypes
的玩法,讲完meta
元数据,我们看到底下还有四个Story
,每种Story
刚好就对应着Button
组件的一种样式,这四个Story
刚好也体现在文档中Button
目录底下
而对于今天我们要调试的组件来讲,每个Story
里面刚好就可以放一些我们想要维护的样式,现在大致的了解了Storybook项目后,我们就要着手使用Storybook来调试Threejs的3D模型了
使用Storybook调试3D模型
首先肯定要在项目中导入Threejs,在终端下执行以下命令
前者是安装Threejs,后者是一个类型检查库,好了之后我们也在stories
目录底下新建两个文件,一个是TestThree.tsx
,另一个是TestThree.stories.ts
,然后在TestThree.tsx
中加入需要调试的组件,我将最近在调的一个粒子球的demo加了进去,简单的说一下实现方式,渲染器,场景相机的初始化就省略了,粒子球的实现方式如下
这里用到了球面坐标体系THREE.Spherical()
,每一个粒子的几何体是一个半径为1的球体SphereGeometry
,所有粒子最终都会添加到一个THREE.Object3D
里面,每一个粒子在球面上的坐标是通过这个粒子距离原点坐标的距离,方位角大小与极角大小来决定,这里将球面的垂直与水平方向各分成三十份,通过遍历计算出每一个极角与方位角的大小,最后再调用setFromSpherical
函数得到向量坐标,得到坐标后新建个Mesh
物体,这里用的材质是标准材质,颜色设置为蓝色,物体的位置坐标就是刚才计算得到的向量坐标,得到的效果如下
效果有了,但是如果我们想要尝试下更改球体的大小,球体的颜色,或者使用其他几何体,那么就要不断去更改一些特定的变量再编译才能看到效果,而在Storybook上就不用这么麻烦了,可以将需要调的变量作为这个组件的入参传进来,在Storybook的控制面板中来调试这些变量,在预览界面就能实时看到变化,咱先试试看改变下球体的颜色,创建个interface
,里面声明个代表球体颜色的变量bgcolor
将球体设置颜色的地方换成读取属性bgcolor
的值
然后为了能够在入参发生改变后页面可以刷新,我们需要在副作用函数useEffect
上监听参数的变化
现在就能在Storybook上更新粒子球的颜色了,我们试试看
这里发现改变颜色后,并不是在原有的视图上更改球体颜色,而是新建了一个新的视图,为啥会有这样的现象呢?问题代码出在渲染器初始化的地方
当参数发生改变触发副作用函数重新执行了一遍内部代码的时候,我们的容器始终会将新建出来的渲染器添加进来,造成的结果就是容器内部的子节点不断增多,所以我们需要在这一步做个判断,当容器内部已有子节点的时候,需要将旧节点换成我们的新节点,调用replaceChild
函数,代码如下
这样就能解决上面改变属性会多出来一个视图的问题,我们再试下
现在就可以在原有的视图上更改颜色了,调通了这一步,接下来就容易多了,我们只需要确定好需要调哪些变量就可以了,比如这里希望粒子距离原点的距离可以是个随机值,也就是呈现的样式不是一个球体,那么这里还需要添加如下两个属性
当ballsize
的值不为0时,说明粒子与原点的距离开始随机变化了,整个样式也就不是一个球体,我们在默认的Story
样式中添加一下这俩属性值,设置一下初始值
然后在代码中,在对应的位置上将新增的这两个属性添加进来
这样一来,在Storybook的控制面板中,我们也可以实时更改我们物体的大小,来试试看
总结
在Storybook中如何调试Threejs样式大概介绍到这了,举的例子都是蛮简单的,改颜色改大小这些,像改变几何体,添加纹理以及gltf模型等这些有兴趣的小伙伴可以自己去试一下,主打的就是一个零编译快速查看效果的目的,希望这篇文章可以给工作中刚好遇到过类似问题的小伙伴提供一些帮助或者思路