前言
今天的主角是 THREEJS , three.js 是用 JavaScript 编写的 WebGl 第三方库,是前端实现 3D 可视化比较流行的一个方案. three.js 是一个比较复杂的库,它包含了许多 Api(如比较基本的 创建一个场景、摄像机、渲染器等),以及拓展的一些插件 想在短时间内熟练掌握还是比较困难的,可阅读three.js 中文文档,阅读文档还是比较枯燥和难懂的,在你掌握创建一个场景、相机、渲染器三个基本元素的情况下,建议阅读 three.js 官方提供的案例代码遇到不懂的代码再返回去查看中文文档.案例github地址(tip:examples目录下)
使用 three.js 写一个 3D 87键键盘(最后附带源码地址)
第一步:引入 threeJs 以及相关插件
// 引入 three.js
import * as THREE from '../build/three.module.js';
// TweenJS是使用JavaScript中的一个简单的补间动画库,在此案例中主要是实现 '键' 的动画
import { TWEEN } from '../common/jsm/libs/tween.module.min.js';
// 引入 TrackballControls 他是最常用的控件,可以使用鼠标轻松的移动、平移,缩放场景。
import { TrackballControls } from '../common/jsm/controls/TrackballControls.js';
// 将 DOM 元素转为 3d 元素;
import { CSS3DRenderer, CSS3DObject } from '../common/jsm/renderers/CSS3DRenderer.js';
第二步:声明 threeJs 场景内的基本元素和 87 的键 JSON 数据
// 87 键的 JSON 数据(在此只写了部分数据)
// 键名 键名二 键图标 键位置(x,y) 键宽高(height,width);
const table = [
"Esc", "", "", 1, 1,160,160,
"F1", "", "iconfont icon-jingyin", 3, 1,160,160,
"F2", "", "iconfont icon-jianyinliang", 4, 1,160,160,
"F3", "", "iconfont icon-jiayinliang", 5, 1,160,160,
"F4", "", "", 6, 1,160,160,
"F5", "", "", 7.5, 1,160,160,
"F6", "", "", 8.5, 1,160,160,
"F7", "", "", 9.5, 1,160,160,
"F8", "", "", 10.5, 1,160,160,
... // 省略
'Win','','',2,6,160,160,
'Alt','','',3,6,160,160,
'_____________','','',7.54,6,1280,160,
'Alt','','',12.04,6,160,160,
'Fn','','',13.04,6,160,160,
'↖','','',14.04,6,160,160,
'Ctrl','','',15.04,6,160,160,
'←','','',16.53,6,160,160,
'↓','','',17.53,6,160,160,
'→','','',18.53,6,160,160,
];
// 场景、相机、渲染器、控件对象
let scene,camera,renderer,controls;
// 存放每个键的初始状态
let objects = [];
// 存放每个键的最终状态
const targets = {table:[]};
第三步:定义一个初始化函数并在初始化函数中赋值上述声明的变量
function init(){
scene = new THREE.Scene(); // 场景(必要)
scene.position.y = -400; // 场景 y 轴上的位置
camera = new THREE.PerspectiveCamera(40,window.innerWidth/window.innerHeight,1,10000); // 相机(必要)
camera.position.z = 3000; // 相机在 z 轴上的位置
// 循环 table 创建对应 '键' 的 html 元素;
// 生成 键 的初始位置 和 最终位置
for( let i = 0; i<table.length;i+=7){
// 每个键(宽高背景颜色)
const element = document.createElement('div');
element.className = 'element';
element.style.backgroundColor = 'rgba(0,0,0,'+(Math.random()*0.5+0.25)+')';
element.style.height = table[i+6] + 'px';
element.style.width = table[i+5] + 'px';
element.style.display = 'inline-block';
element.addEventListener('click',function(){
clickHandle(table[i])
})
// 键的名称
const symbol = document.createElement('span');
symbol.className = 'symbol';
symbol.textContent = table[i];
element.appendChild(symbol);
// 键的二级名称
if(table[i+1]!=''){
const details = document.createElement('span');
details.className = 'detail';
details.innerHTML = table[i+1];
element.appendChild(details);
}
// 键的图标
if(table[i+2]!=''){
const iconBox = document.createElement('div');
const icon = document.createElement('i');
iconBox.appendChild(icon);
iconBox.className = 'icon';
icon.className = table[i+2]
element.appendChild(iconBox);
}
// 初始随机生成键的位置
const objectcss = new CSS3DObject( element );
objectcss.position.x = Math.random()*4000 - 2000;
objectcss.position.y = Math.random()*4000 - 2000;
objectcss.position.z = Math.random()*4000 - 2000;
scene.add(objectcss);
objects.push(objectcss);
// 键的最后位置
const object = new THREE.Object3D();
object.position.x = (table[i+3]*160) - 1330;
object.position.y = -(table[i+4]*170) + 990;
targets.table.push(object);
}
renderer = new CSS3DRenderer(); // 渲染器(必要)
renderer.setSize(window.innerWidth,window.innerHeight);// 渲染器大小
document.getElementById('container').appendChild(renderer.domElement);
controls = new TrackballControls( camera, renderer.domElement );// 控件作用于这个场景,使场景可控
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener('change',render);
transform( targets.table, 2000 );// 动画
}
第四步:根据 键 随机生成的初始化位置(上述定义的objects) 动画到 键的 最终位置(上述定义的targets.table)
// 动画 使用到了 TWEEN 插件
function transform(targets,duration){
TWEEN.removeAll();
// 遍历初始化位置,从初始化位置动画到最终位置
for(let i = 0;i<objects.length;i++){
const object = objects[i];
const target = targets[i];
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();
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();
new TWEEN.Tween( this )
.to( {}, duration * 2 )
.onUpdate(render)
.start();
}
}
第五步:在基本元素都具备的条件下 定义渲染函数并在动画更新的时候调用(重新渲染)
function render() {
renderer.render( scene, camera );
}
第七步:更新动画
function animate() {
requestAnimationFrame( animate );
TWEEN.update();
controls.update();
}
last效果图
源码
- 注意:源码下载后不能直接在浏览器中预览效果,因为依赖的静态资源会发生跨域问题,最简单的方法是使用 vscode 的 插件(Live Server)来打开html文件,它创建的服务会进行资源托管就不会有跨域的问题啦~