年会签到做到最后特效实现的部分,使用 Js 来不断绘制 Canvas 会出现擦除背景图的情况,而且并不流畅,
所以又考虑了下 CSS3 的动画属性,可是 Javascript 并不能控制 CSS3 动画。
昨天折腾到晚上 10 点还没怎么好的思路,今天看到一个开源 WebGL Js 库 👉 ThreeJs.org
中间不断重写和踩坑的过程就省略了,主要自己也记不得了….
ThreeJS 的 examples 中有一个 Demo 和需求简直一摸一样 👉 CSS3D - Periodic Table
读了一下源码并且添加了释。
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 分