前言
上篇文章 尝试实现了国外大佬用Web做出来跨窗口渲染动画效果 – 掘金 反响很大,但是仅仅只是实现了跨窗口动画效果,严格说就没有动画,还有些bug和遗憾,尤其是粒子效果,得入three.js
坑,怎么办?跳啊!
硬肝了两天,实在肝不动了,看效果吧。
第一版v2效果,大粒子,粒子数量较少:
第二版v2.1,小粒子,粒子数量多:
three.js
官方文档:threejs.org/,中文文档:three.js docs
我第一次接触three.js
,之前只是听说过,比如能弄车展啥的,感觉很厉害,就想借此机会学习下,跳进坑里才发现,这坑也太深了。随便找个教程,里面各种名词就给我弄吐了。
先按文档 three.js docs 画个立方体,跑起来了,但是我想要球体啊,还有粒子围着中心转,这么多api学不起啊,搜教程也是杂乱无章,无从学起,咋整?找现成的啊!
看到官网有很多现成的例子,找了一个相近的:threejs.org/examples/#w…,截了个静态图长这样:
找源码:three.js/examples/we…,copy到本地,代码就200行,用到的three api就几个,搜下api大概了解代码各部分的功能,基本都能看懂,然后删了多余功能,一个粒子绕球中心旋转功能就出来了。
现在关于粒子移动、渲染、球体旋转缩放等变化api都已经基本搞懂了,然后就是痛苦折磨的计算调试了,不想再回忆了。
动画效果的移动都是靠循环对象、计算坐标,改变粒子的position
来实现的,感觉应该会有更好的现成api能简化这个过程,而且有缓冲、阻尼效果等。如果有更好的例子,欢迎大佬分享。
总结下用到的api吧,就几个:
构造方法
THREE.PerspectiveCamera
:透视投影相机,3D场景的渲染中使用得最普遍的投影模式。THREE.Scene
、THREE.WebGLRenderer
:场景和渲染器。THREE.TextureLoader
:创建纹理,用于加载粒子贴图。THREE.SpriteMaterial
:创建精灵材质。THREE.Sprite
:创建精灵,用于表示粒子。THREE.Group
:创建对象容器,用于整体控制多个粒子,达到旋转等效果。
属性
.position.xyz
:坐标位移;.rotation.xyz
:粒子绕球体旋转;.position.multiplyScalar(radius)
:对三个向量xyz上分别乘以给定标量radius,用于设置粒子距球体中心距离;.scale.set
:设置粒子自身的缩放.visible
:控制Group或粒子显隐;
难点
在THREE.PerspectiveCamera
透视投影相机下,由于是模拟人的眼睛从远处看的,所以会导致坐标上的单位跟html里的像素单位是不一致的,有一定的比例。但是判断浏览器窗口位置都是像素单位的,所以得算出这个比例、或者找到一种办法让两个单位是一致的。在外网搜到一个方案:forum.babylonjs.com/t/how-to-se…
const perspective = 800;
const fov = (180 * (2 * Math.atan(window.innerHeight / 2 / perspective))) / Math.PI;
const camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, perspective);
这样设置相机后,俩个单位就是一致的。至于原理。。。看见Math.atan
就头大,过!
BroadcastChannel
BroadcastChannel
的api很简单,在一个窗口中postMessage
,另一个窗口就会通过message
事件接受到了。
const channel = new BroadcastChannel('editor_channel');
channel.postMessage({ aa: '123' });
channel.addEventListener('message', ({ data }) => {
console.log(data);
});
在此例逻辑是,进页面初始化时、或者坐标改变时,需要把当前窗口坐标postMessage
发送到别的窗口中,然后再把所有窗体坐标数据都存在js全局变量里使用。
但是这里有个问题,如果刷新其中一个窗口时,没办法立即获取别的窗口数据,因为别的窗口只有在坐标变化时才会发送数据(为了提高效率,不会在requestAnimationFrame
里一直发数据),这样就得主动postMessage
一个标记到别的窗口,然后别的窗口再把自己的数据postMessage
回来,是个异步过程,有些繁琐。
使用上不比LocalStorage
简单多少,不过BroadcastChannel
确实可以解决LocalStorage
的全局影响和缓存不自动清空问题。有兴趣可以自己实现下。(可以重写storage.js
里方法)
优化窗口数据监听与更新
- 注册
window storage
事件,监听storage变化时(当其它窗口位置变化时),判断最新窗口总数,当数量变化时,在当前窗口重新实例化所有球体及粒子对象。 - 注册
window resize
事件,更新摄像机比例和渲染器size。 - 将所有窗口数据保存在js全局变量里,用于在
requestAnimationFrame
中读取渲染动画,并且只在需要时更新:- 其它窗口位置变化时(通过
window storage
事件); - 在
requestAnimationFrame
中判断当前窗口位置变化时(比较全局变量与当前window位置),更新全局变量和storage;
- 其它窗口位置变化时(通过
通过以上逻辑优化,可以有效提高渲染速度,减少代码重复执行,减小客户端压力。
待改进
three.js
实现上:学习的还是太浅了,有些动画效果应该会有更好的实现方式,希望有大佬能指点下。three.js
效果:跟国外原大佬比不了,他那是粒子,我这个就是个球。- 拖动窗口位置时的球体移动阻尼效果,这个实现了下,有了个效果,但是卡顿感明显,不顺畅,而且在连线动画下效果更差。
- 当改变窗口大小时,球体大小会随着窗口大小变化,想固定大小没找到解决方法,然后计算球体位置也没有考虑窗体大小,所以现在多窗口要求窗口大小必须是一样的。
- 球体之间的连线粒子移动效果不佳,特别在窗口移动时,还需优化算法。
总结
总结下相比之前的例子 尝试实现了国外大佬用Web做出来跨窗口渲染动画效果,有以下提升:
- 引入
three.js
,画出了球体、粒子旋转动画,多窗口球体,球体间粒子连线动画效果。 BroadcastChannel
代替LocalStorage
。(技术选型没选上,未实现)- 支持多个窗口(理论上没有限制),并且窗口重叠时不会有连线缺失。
跨窗口通信、存储窗口坐标、在每个窗口画出所有球体和连线,这个机制流程已经很成熟了,没有太大的优化提升空间了,所以要实现国外大佬视频效果,就只剩three.js
了,实在是学不动了,水太深。
源码已上传至GitHub,代码里有详细注释,希望能有所帮助:github.com/markz-demo/…
做了两版效果,可以通过代码里注释查看效果,README.md 中有说明。
Demo:markz-demo.github.io/mark-cross-…
本文同步推送到了公众号:「前端程序员大熊」,求个关注,求个赞 :)
文章来源于互联网:终于把国外大佬的跨窗口量子纠缠粒子效果给肝出来