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

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

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

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

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

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 分