年会开发日记01 - 3D签到墙

年会签到做到最后特效实现的部分,使用 Js 来不断绘制 Canvas 会出现擦除背景图的情况,而且并不流畅,
所以又考虑了下 CSS3 的动画属性,可是 Javascript 并不能控制 CSS3 动画。

昨天折腾到晚上 10 点还没怎么好的思路,今天看到一个开源 WebGL Js 库 👉 ThreeJs.org

前置文章 Canvas 识别图片内容并使用用户头像拼接

中间不断重写和踩坑的过程就省略了,主要自己也记不得了….

ThreeJS 的 examples 中有一个 Demo 和需求简直一摸一样 👉 CSS3D - Periodic Table

读了一下源码并且添加了释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import * as THREE from '../build/three.module.js';
import { TWEEN } from './jsm/libs/tween.module.min.js';
import { TrackballControls } from './jsm/controls/TrackballControls.js';
import { CSS3DRenderer, CSS3DObject } from './jsm/renderers/CSS3DRenderer.js';
var table = [...];

var camera, scene, renderer;
var controls;
var objects = [];
var targets = { table: [], sphere: [], helix: [], grid: [] };

init();
animate();

function init() {
// 创建一个透视相机 视角40,宽高比窗口内宽度/高度,近场1,远场1000
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 3000; // 相机置于 z - 3000
scene = new THREE.Scene(); // 创建场景对象

// table 元素周期表

// 循环创建所有数据
for ( var i = 0; i < table.length; i += 5 ) {

// 创建一个新的div元素
var element = document.createElement( 'div' );
element.className = 'element'; // 设置类名为 element
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')'; // div.element 设置背景色 rgba

// 创建一个新的div元素
var number = document.createElement( 'div' );
number.className = 'number'; // 设置类名为 number
number.textContent = ( i / 5 ) + 1; // div.number 设置文本内容为 i/5 +1
element.appendChild( number ); // 把 div.number 添加到 div.element 内

// 创建一个新的div元素
var symbol = document.createElement( 'div' );
symbol.className = 'symbol'; // div.symbol 设置类名为symbol
symbol.textContent = table[ i ]; // div.symbol 的文本内容为 table[i]的内容
element.appendChild( symbol ); // 把 div.symbol 添加到 div.element 内

// 创建一个新的div元素
var details = document.createElement( 'div' );
details.className = 'details'; // 设置类名为 details
// div.details 的 html 赋值为 table[ i + 1 ] + '<br>' + table[ i + 2 ]
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details ); // 把 div.details 添加到 div.element 内

// 创建一个新的 CSS3D 对象
var object = new CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000; // 设置 3D对象的 x轴 位置 区间 -2000 ~ 2000
object.position.y = Math.random() * 4000 - 2000; // 设置 3D对象的 y轴 位置
object.position.z = Math.random() * 4000 - 2000; // 设置 3D对象的 z轴 位置

scene.add( object ); // CSS3D 添加对象到场景中
objects.push( object ); // 把 CSS3D 填加到 objects 中

// Object3D
// New一个新的三维对象
var object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330; // 设置物体的 x轴 位置
object.position.y = - ( table[ i + 4 ] * 180 ) + 990; // 设置物体的 y轴 位置

targets.table.push( object ); // 把object添加到 targets.table 中

}

// sphere 球形数据
var vector = new THREE.Vector3(); // 创建一个新的三维向量
// 循环创建所有数据
for ( var i = 0, l = objects.length; i < l; i ++ ) {

var phi = Math.acos( - 1 + ( 2 * i ) / l ); // 设置 分割比/phi
var theta = Math.sqrt( l * Math.PI ) * phi; // 设置 半径/l 角度/theta

// 创建一个新的三维对象
var object = new THREE.Object3D();

object.position.setFromSphericalCoords( 800, phi, theta ); // 设置向量 在球体坐标中
vector.copy( object.position ).multiplyScalar( 2 ); // 复制向量给 object,将该向量与 2 相乘。

object.lookAt( vector ); // 将 object 朝向三维向量的方向
targets.sphere.push( object ); // 把 object 添加到 targets.sphere 中
}

// helix 螺旋圆柱的数据

var vector = new THREE.Vector3(); // 创建一个新的三维向量

// 循环创建所有数据
for ( var i = 0, l = objects.length; i < l; i ++ ) {

var theta = i * 0.175 + Math.PI; // 设置 角度 theta
var y = - ( i * 8 ) + 450; // 设置 y 这边我修改成了 y = i * 1 - (l * 1) / 2 按对象数量去修改y值

// 创建一个新的三维对象
var object = new THREE.Object3D();

object.position.setFromCylindricalCoords( 900, theta, y ); // 设置向量 在圆柱体中 900为半径 y为x-z平面以上的高度
vector.x = object.position.x * 2; // 设置 x轴 位置
vector.y = object.position.y; // 设置 y轴 位置
vector.z = object.position.z * 2; // 设置 z轴 位置

object.lookAt( vector ); // 将object朝向三维向量的方向
targets.helix.push( object ); // 把object添加到 targets.helix 中
}

// grid 栅格的数据

// 循环创建所有数据
for ( var i = 0; i < objects.length; i ++ ) {

// 创建一个新的三维对象
var object = new THREE.Object3D();

object.position.x = ( ( i % 5 ) * 400 ) - 800; // 设置 x轴 位置 %5为行数 400为间隔
object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800; // 设置 y轴 位置 %5为列数 400为间隔
object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000; // 设置 z轴 位置 25为一个平面的元素数量
/*
* @description 如果要自己修改行列数,可以修改成
* @param {Integer} row - 行数.
* @param {Integer} col - 列数.
* @param {Integer} interval - 间隔.
* const row = 4
* const col = 8
* const interval = 40
* object.position.x = (i % row) * interval - (row * interval) / 2 + interval / 2;
* object.position.y = (Math.floor(i / row) % col) * interval - (col * interval) / 2 + interval / 2;
* object.position.z = -Math.floor(i / row / col) * interval;
*
*/
targets.grid.push( object ); // 把object添加到 targets.grid 中

}

// renderer
renderer = new CSS3DRenderer(); // 创建一个新的CSS 3D渲染器
renderer.setSize( window.innerWidth, window.innerHeight ); // 将渲染器尺寸重新调整为 innerWidth innerHeight
document.getElementById( 'container' ).appendChild( renderer.domElement ); // 将渲染器添加到容器.container 中

// controls
controls = new TrackballControls( camera, renderer.domElement ); // 创建一个新的轨迹球控件
controls.minDistance = 500; // 设置最小缩放数
controls.maxDistance = 6000; // 设置最大缩放数
controls.addEventListener( 'change', render ); // 添加 change 事件监听,触发render事件

var button = document.getElementById( 'table' ); // 把 #table 赋值给 button 变量
button.addEventListener( 'click', function () { // 给 button 添加点击事件监听,点击后 触发 transform 事件
transform( targets.table, 2000 );
}, false );

var button = document.getElementById( 'sphere' ); // 把 #sphere 赋值给 button 变量
button.addEventListener( 'click', function () { // 给 button 添加点击事件监听,点击后 触发 transform 事件
transform( targets.sphere, 2000 );
}, false );

var button = document.getElementById( 'helix' ); // 把 #sphere 赋值给 button 变量
button.addEventListener( 'click', function () { // 给 button 添加点击事件监听,点击后 触发 transform 事件
transform( targets.helix, 2000 );
}, false );

var button = document.getElementById( 'grid' ); // 把 #grid 赋值给 button 变量
button.addEventListener( 'click', function () { // 给 button 添加点击事件监听,点击后 触发 transform 事件
transform( targets.grid, 2000 );
}, false );

transform( targets.table, 2000 ); // 切换到 table 效果,动作时间为 2000 ms

//

window.addEventListener( 'resize', onWindowResize, false ); // 监听视窗的 resize 事件,每次改变窗口大小是触发 onWindowResize 事件

}

// transform
function transform( targets, duration ) {
TWEEN.removeAll(); // 移除所有补间数据
// 循环所有数据
for ( var i = 0; i < objects.length; i ++ ) {
var object = objects[ i ]; // 获取当前对象
var target = targets[ i ]; // 获取当前对象对应形状的数据
// 创建坐标轴移动补间动画,to(目标坐标,动画持续时间) easing(弹性值) start()开始
new TWEEN.Tween( object.position )
.to( { x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
// 创建旋转角度补间动画,to(目标坐标,动画持续时间) easing(弹性值) start()开始
new TWEEN.Tween( object.rotation )
.to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
}
// 设置数据更新时render事件,优化render 这里其实我没有怎么明白。
new TWEEN.Tween( this )
.to( {}, duration * 2 )
.onUpdate( render )
.start();
}
// 窗口大小改变时
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight; // 重新设置相机宽高比
camera.updateProjectionMatrix(); // 更新投影矩阵
renderer.setSize( window.innerWidth, window.innerHeight ); // 重新设置场景渲染器大小
render(); // 触发渲染更新
}
// 动画事件触发渲染更新
function animate() {
// 每次帧动画更新是触发本身
requestAnimationFrame( animate );
// 补间动画刷新
TWEEN.update();
// 控制器刷新
controls.update();
}
// 渲染函数
function render() {
// 渲染当前场景
renderer.render( scene, camera );
}

感觉都不用怎么说了。稍微修改一下 element 元素内的内容就可以了。

但是元素不宜多,不然元素移动的时候严重掉帧。

尽量使用元素的组合去作整体的运动 -> Group, 组合之后也可以单独移动内部的元素。

移动镜头来进行元素移动也可以节省一部分的性能消耗 -> Camera 移动 Demo

隔了太久了,先写这么多,等想起来什么了在补全

2020 年 1 月 6 日 17 点 03 分