阅读three.js 官网示例 --酷炫的元素周期表

一、示例介绍

这是一个3D酷炫的化学元素周期表示例,该示例包含普通TABLE(表格)、SPHERE(球体)、HELIX(螺旋)、GRID(网格)四种状态。状态之间的切换借助了 TWEEN 动画库, 使得切换动画丝滑流畅。

TABLE:

SPHERE

HELIX:

GRID

二、知识点介绍

1. type="importmap"

每个属性都对应着一个映射。映射的左边是 import 指定其的名称,而右边是指定器应该映射到的相对或绝对URL.

xml 复制代码
<script type="importmap">
{
  "imports": {
    "dayjs": "https://cdn.skypack.dev/dayjs@1.10.7",
  }
}
</script>
<script type="module">
  import dayjs from 'dayjs';

  console.log(dayjs('2019-01-25').format('YYYY-MM-DDTHH:mm:ssZ[Z]'));
</script>

注意:

(1) 在 import map 中出现包并不意味着它一定会被浏览器加载。任何没有被页面上的 script 使用的模块都不会被浏览器加载,即使它存在于import map 中 。

(2) 导入映射中的映射不会影响诸如<script>标签的 src 属性之类的位置。因此,如你的使用<script src="/app.js">之类的内容,浏览器将试图在该路径上下载一个字面上的app.js文件,而不管 import map 中的内容如何。

(3) 这个script 标签必须放在 document 中的中第一个 <script type="module">标签之前(最好是在<head>中),以便在进行模块解析之前对它进行解析。

2. 定义元素周期表数组

table变量定义了元素周期表相关的数据,其中一行(5个数据)代表一个元素。 以第一行的数据为例,进行说明

(1)H - 元素符号 (2)Hydrogen - 元素英文名称 (3)1.00794 - 原子量 (4)1 - 该元素位于元素表第1列 (5)1 - 该元素位于元素表第1行

js 复制代码
const table = [
        'H', 'Hydrogen', '1.00794', 1, 1,
        'He', 'Helium', '4.002602', 18, 1,
        ......
];

3. 生成TABLE形式元素周期表

以下代码主要做了两件事:

(1)生成多个化学素html结构,以H为例:

html 复制代码
<div class="element">
	<div class="number">1</div>
	<div class="symbol">H</div>
	<div class="details">Hydrogen<br>1.00794</div>
</div>

(2)设置每个元素在3D场景中的位置,并将其存储在targets.table对象中。

详细代码:

js 复制代码
for ( let i = 0; i < table.length; i += 5 ) {

    // 元素外层div
    const element = document.createElement( 'div' );
    element.className = 'element';
    element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';

    // 原子序数
    const number = document.createElement( 'div' );
    number.className = 'number';
    number.textContent = ( i / 5 ) + 1;
    element.appendChild( number );

    // 元素符号
    const symbol = document.createElement( 'div' );
    symbol.className = 'symbol';
    symbol.textContent = table[ i ];
    element.appendChild( symbol );

    // 元素英文名称 + 原子量
    const details = document.createElement( 'div' );
    details.className = 'details';
    details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
    element.appendChild( details );

    // 将元素DOM对象传给 CSS3DObject对象,随机生成位置
    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 );

    // 设置每个元素在table的具体位置
    const object = new THREE.Object3D();
    object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
    object.position.y = - ( table[ i + 4 ] * 180 ) + 990;

    targets.table.push( object );

}

4. 生成SPHERE形式元素周期表

这算法看得不是很懂

js 复制代码
// sphere

const vector = new THREE.Vector3();

for ( let i = 0, l = objects.length; i < l; i ++ ) {
        // Math.acos: 反余弦值
        const phi = Math.acos( - 1 + ( 2 * i ) / l );
        const theta = Math.sqrt( l * Math.PI ) * phi; // Math.sqrt 返回一个数的平方根,Math.PI 表示一个圆的周长与直径的比例,约为 3.14159

        // Object3D: 三维物体
        const object = new THREE.Object3D();
        // .setFromSphericalCoords ( radius : Float, phi : Float, theta : Float ) : this   从球坐标中的radius、phi和theta设置该向量。
        // radius: 半径值
        // phi: 与y(up)轴的极角(以弧度为单位)。默认值为0
        // theta: 绕y(up)轴的赤道角(方位角)(以弧度为单位)。 默认值为 0。
        object.position.setFromSphericalCoords( 800, phi, theta );

        // .multiplyScalar ( s : Float ) : this 将该向量与所传入的标量s进行相乘
        vector.copy( object.position ).multiplyScalar( 2 );

        object.lookAt( vector );

        targets.sphere.push( object );

}

5. 生成HELIX形式元素周期表

js 复制代码
for ( let i = 0, l = objects.length; i < l; i ++ ) {

        const theta = i * 0.175 + Math.PI;
        const y = - ( i * 8 ) + 450;

        const object = new THREE.Object3D();

        // .setFromCylindricalCoords ( radius : Float, theta : Float, y : Float )   从圆柱坐标中的radius、theta和y设置该向量。
        // radius - 从原点到x-z平面上一点的距离 默认值为 1.0.
        // theta - 在x-z平面内的逆时针角度,以z轴正方向的计算弧度。默认值为0。
        // y - x-z平面以上的高度 默认值为 0.
        object.position.setFromCylindricalCoords( 900, theta, y );

        vector.x = object.position.x * 2;
        vector.y = object.position.y;
        vector.z = object.position.z * 2;

        object.lookAt( vector );

        targets.helix.push( object );

}

6. 生成GRID形式元素周期表

网格布局最终排列方式是,5 * 5 * 5 的网格形式排列, x y z 轴均分别取5个位置点。由于元素一共是118个,但这种排列方式可以容纳 555 = 125个元素。所以该网格模式可以保证元素周期表的元素都有其唯一的坑位。

js 复制代码
for ( let i = 0; i < objects.length; i ++ ) {

    const object = new THREE.Object3D();

    // x 值: -800  -400 0 400 800
    object.position.x = ( ( i % 5 ) * 400 ) - 800; 
    // y轴也是排列5个
    object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
    // 注元素一共118个,则 Math.floor( i / 25 ) 值为 0 - 4, 则z轴排5个
    object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;

    targets.grid.push( object );

}

7. TWEEN 动画

元素周期表的几种排版切换,使用了TWEEN补间动画实现,使得切换更为流畅丝滑。下面将会简单介绍下TWEEN库的使用。
(1)补间功能方法

  • start

    开启补间动画, new TWEEN.Tween().start(time) ,start 方法接受一个参数 time, 如果加入参数,那么补间动画不会立即开始直到特定时刻才会开始。

  • stop

    关闭补间动画new TWEEN.Tween().stop() , 关闭这个正在执行的补间动画

  • repeat

    控制补间重复的次数new TWEEN.Tween().repeat(), repeat 它接受一个参数 , 描述第一个补间完成后 需要多少次重复

  • yoyo

    控制补间重复的模式 , new TWEEN.Tween().yoyo() , 这个功能只有在使用 repeat 时才有效果 , 补间的 行,为将像悠悠球一样来回运动 , 而不是重新开始

  • delay

    控制补间开始前的延迟 new TWEEN.Tween().delay() , 补间开始之前的延迟时间接受一个参数用于控制具 体时间

  • pause

    暂定补间动画 new TWEEN.Tween().pause() , 暂停当前补间运动

  • resume

    恢复补间动画 new TWEEN.Tween().resume() , 恢复这个已经被暂停的补间运动

  • to

    控制补间的运动形式及方向 new TWEEN.Tween().to() , 当tween启动时,Tween.js将读取当前属性值并 应用相对值来找出新的最终值

(2)补间操作方法

  • update

    更新补间动画 TWEEN.update() , 动态更新补间运动一般配合 window.requestAnimationFrame 使用

  • getAll 获取所有的补间组 TWEEN.getAll()

  • removeAll 删除所有的补间组 TWEEN.getAll()

  • add 新增补间 TWEEN.add(tween) (tween) 添加一个特定的补间 var tween=new TWEEN.Tween()

  • remove 删除补间 TWEEN.remove(tween) (tween) 删除一个特定的补间 var tween=new TWEEN.Tween()

  • Group 新增一个补间组 var Group=TWEEN.Group() , new TWEEN.Tween({ x: 1 }, Group) , 将已经配置 好的补间进行分组 , TWEEN.update()TWEEN.removeAll() , 不会影响到已经分好组的补间

(3)补间回调方法

  • onStart()
    new TWEEN.Tween().onStart((obj)=>{}) , 补间开始时执行,只执行一次, 当使用 repeat() 重复补间 时,不会重复运行 , onStart((obj)=>{}) obj 补间对象作为第一个参数传入

  • onStop()
    new TWEEN.Tween().onStop((obj)=>{}) , 当通过 onStop() 显式停止补间时执行,但在正常完成时并且在停止任何可能的链补间之前执行补间',onStop((obj)=>{}) obj 补间对象作为第一个参数传入

  • onUpdate()
    new TWEEN.Tween().onUpdate((obj)=>{}) , 每次补间更新时执行,返回实际更新后的值, onUpdate((obj)=>{}) obj 补间对象作为第一个参数传入

  • onComplete() new TWEEN.Tween().onUpdate((obj)=>{}) , 每次补间更新时执行,返回实际更新后的值, onUpdate((obj)=>{}) obj 补间对象作为第一个参数传入

8. TrackballControls 轨迹球控件

TrackballControls 和 OrbitControls相类似,然而他不能恒定保持摄像机的up向量。这意味着,如果摄像机绕过"北极"和"南极",则不会翻转以保持"右侧朝上"。利用该轨迹球控件,可以实现场景的旋转、缩放、平移等功能。

(1)动作和操控对比说明
动作 操控
在场景中旋转、翻滚相机 按住鼠标左键拖动
放大和缩小 转动鼠标滚轮或按住鼠标中键拖动
在场景中平移 按住鼠标右键拖动
(2)属性
属性 描述
enabled 是否开启控制器,默认true,如果设置为false,对相机的操作将失效
rotateSpeed 相机的旋转速度,默认 3.0
zoomSpeed 相机的缩放速度,默认 1.2
panSpeed 相机的平移速度,默认 0.3
noRotate 是否关闭相机旋转,默认 false
noZoom 是否关闭相机缩放,默认 false
noPan 是否关闭相机移动,默认 false
staticMoving 是否关闭拖拽惯性移动,默认 false
dynamicDampingFactor 拖拽惯性移动阻力,默认 0.2
minDistance 相机距离焦点的最近距离,默认 0
maxDistance 相机距离焦点的最远距离,默认 Infinity(无限远)
(3)方法
方法名 说明
update TrackballControls控件控制相机更新的方法,需要在循环动画函数中调用,通常在render函数中调用
reset 重置相机的方法,该函数可以使相机回到初始位置
dispose 销毁实例化的TrackballControls对象
change 可以监听相机变化事件

三、完整示例代码

js 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>three.js css3d - periodic table</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link type="text/css" rel="stylesheet" href="main.css">
    <style>
            a {
                    color: #8ff;
            }

            #menu {
                    position: absolute;
                    bottom: 20px;
                    width: 100%;
                    text-align: center;
            }

            .element {
                    width: 120px;
                    height: 160px;
                    box-shadow: 0px 0px 12px rgba(0,255,255,0.5);
                    border: 1px solid rgba(127,255,255,0.25);
                    font-family: Helvetica, sans-serif;
                    text-align: center;
                    line-height: normal;
                    cursor: default;
            }

            .element:hover {
                    box-shadow: 0px 0px 12px rgba(0,255,255,0.75);
                    border: 1px solid rgba(127,255,255,0.75);
            }

                    .element .number {
                            position: absolute;
                            top: 20px;
                            right: 20px;
                            font-size: 12px;
                            color: rgba(127,255,255,0.75);
                    }

                    .element .symbol {
                            position: absolute;
                            top: 40px;
                            left: 0px;
                            right: 0px;
                            font-size: 60px;
                            font-weight: bold;
                            color: rgba(255,255,255,0.75);
                            text-shadow: 0 0 10px rgba(0,255,255,0.95);
                    }

                    .element .details {
                            position: absolute;
                            bottom: 15px;
                            left: 0px;
                            right: 0px;
                            font-size: 12px;
                            color: rgba(127,255,255,0.75);
                    }

            button {
                    color: rgba(127,255,255,0.75);
                    background: transparent;
                    outline: 1px solid rgba(127,255,255,0.75);
                    border: 0px;
                    padding: 5px 10px;
                    cursor: pointer;
            }

            button:hover {
                    background-color: rgba(0,255,255,0.5);
            }

            button:active {
                    color: #000000;
                    background-color: rgba(0,255,255,0.75);
            }
    </style>
</head>
<body>

    <div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - periodic table.</div>
    <div id="container"></div>
    <div id="menu">
            <button id="table">TABLE</button>
            <button id="sphere">SPHERE</button>
            <button id="helix">HELIX</button>
            <button id="grid">GRID</button>
    </div>

    <script type="importmap">
            {
                    "imports": {
                            "three": "../build/three.module.js",
                            "three/addons/": "./jsm/"
                    }
            }
    </script>

    <script type="module">

            import * as THREE from 'three';

            import TWEEN from 'three/addons/libs/tween.module.js';
            import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
            import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';

            // 定义元素周期表相关数据
            // 元素符号 英文名 原子量 第几列  第几行
            const table = [
                    'H', 'Hydrogen', '1.00794', 1, 1,
                    'He', 'Helium', '4.002602', 18, 1,
                    'Li', 'Lithium', '6.941', 1, 2,
                    'Be', 'Beryllium', '9.012182', 2, 2,
                    'B', 'Boron', '10.811', 13, 2,
                    'C', 'Carbon', '12.0107', 14, 2,
                    'N', 'Nitrogen', '14.0067', 15, 2,
                    'O', 'Oxygen', '15.9994', 16, 2,
                    'F', 'Fluorine', '18.9984032', 17, 2,
                    'Ne', 'Neon', '20.1797', 18, 2,
                    'Na', 'Sodium', '22.98976...', 1, 3,
                    'Mg', 'Magnesium', '24.305', 2, 3,
                    'Al', 'Aluminium', '26.9815386', 13, 3,
                    'Si', 'Silicon', '28.0855', 14, 3,
                    'P', 'Phosphorus', '30.973762', 15, 3,
                    'S', 'Sulfur', '32.065', 16, 3,
                    'Cl', 'Chlorine', '35.453', 17, 3,
                    'Ar', 'Argon', '39.948', 18, 3,
                    'K', 'Potassium', '39.948', 1, 4,
                    'Ca', 'Calcium', '40.078', 2, 4,
                    'Sc', 'Scandium', '44.955912', 3, 4,
                    'Ti', 'Titanium', '47.867', 4, 4,
                    'V', 'Vanadium', '50.9415', 5, 4,
                    'Cr', 'Chromium', '51.9961', 6, 4,
                    'Mn', 'Manganese', '54.938045', 7, 4,
                    'Fe', 'Iron', '55.845', 8, 4,
                    'Co', 'Cobalt', '58.933195', 9, 4,
                    'Ni', 'Nickel', '58.6934', 10, 4,
                    'Cu', 'Copper', '63.546', 11, 4,
                    'Zn', 'Zinc', '65.38', 12, 4,
                    'Ga', 'Gallium', '69.723', 13, 4,
                    'Ge', 'Germanium', '72.63', 14, 4,
                    'As', 'Arsenic', '74.9216', 15, 4,
                    'Se', 'Selenium', '78.96', 16, 4,
                    'Br', 'Bromine', '79.904', 17, 4,
                    'Kr', 'Krypton', '83.798', 18, 4,
                    'Rb', 'Rubidium', '85.4678', 1, 5,
                    'Sr', 'Strontium', '87.62', 2, 5,
                    'Y', 'Yttrium', '88.90585', 3, 5,
                    'Zr', 'Zirconium', '91.224', 4, 5,
                    'Nb', 'Niobium', '92.90628', 5, 5,
                    'Mo', 'Molybdenum', '95.96', 6, 5,
                    'Tc', 'Technetium', '(98)', 7, 5,
                    'Ru', 'Ruthenium', '101.07', 8, 5,
                    'Rh', 'Rhodium', '102.9055', 9, 5,
                    'Pd', 'Palladium', '106.42', 10, 5,
                    'Ag', 'Silver', '107.8682', 11, 5,
                    'Cd', 'Cadmium', '112.411', 12, 5,
                    'In', 'Indium', '114.818', 13, 5,
                    'Sn', 'Tin', '118.71', 14, 5,
                    'Sb', 'Antimony', '121.76', 15, 5,
                    'Te', 'Tellurium', '127.6', 16, 5,
                    'I', 'Iodine', '126.90447', 17, 5,
                    'Xe', 'Xenon', '131.293', 18, 5,
                    'Cs', 'Caesium', '132.9054', 1, 6,
                    'Ba', 'Barium', '132.9054', 2, 6,
                    'La', 'Lanthanum', '138.90547', 4, 9,
                    'Ce', 'Cerium', '140.116', 5, 9,
                    'Pr', 'Praseodymium', '140.90765', 6, 9,
                    'Nd', 'Neodymium', '144.242', 7, 9,
                    'Pm', 'Promethium', '(145)', 8, 9,
                    'Sm', 'Samarium', '150.36', 9, 9,
                    'Eu', 'Europium', '151.964', 10, 9,
                    'Gd', 'Gadolinium', '157.25', 11, 9,
                    'Tb', 'Terbium', '158.92535', 12, 9,
                    'Dy', 'Dysprosium', '162.5', 13, 9,
                    'Ho', 'Holmium', '164.93032', 14, 9,
                    'Er', 'Erbium', '167.259', 15, 9,
                    'Tm', 'Thulium', '168.93421', 16, 9,
                    'Yb', 'Ytterbium', '173.054', 17, 9,
                    'Lu', 'Lutetium', '174.9668', 18, 9,
                    'Hf', 'Hafnium', '178.49', 4, 6,
                    'Ta', 'Tantalum', '180.94788', 5, 6,
                    'W', 'Tungsten', '183.84', 6, 6,
                    'Re', 'Rhenium', '186.207', 7, 6,
                    'Os', 'Osmium', '190.23', 8, 6,
                    'Ir', 'Iridium', '192.217', 9, 6,
                    'Pt', 'Platinum', '195.084', 10, 6,
                    'Au', 'Gold', '196.966569', 11, 6,
                    'Hg', 'Mercury', '200.59', 12, 6,
                    'Tl', 'Thallium', '204.3833', 13, 6,
                    'Pb', 'Lead', '207.2', 14, 6,
                    'Bi', 'Bismuth', '208.9804', 15, 6,
                    'Po', 'Polonium', '(209)', 16, 6,
                    'At', 'Astatine', '(210)', 17, 6,
                    'Rn', 'Radon', '(222)', 18, 6,
                    'Fr', 'Francium', '(223)', 1, 7,
                    'Ra', 'Radium', '(226)', 2, 7,
                    'Ac', 'Actinium', '(227)', 4, 10,
                    'Th', 'Thorium', '232.03806', 5, 10,
                    'Pa', 'Protactinium', '231.0588', 6, 10,
                    'U', 'Uranium', '238.02891', 7, 10,
                    'Np', 'Neptunium', '(237)', 8, 10,
                    'Pu', 'Plutonium', '(244)', 9, 10,
                    'Am', 'Americium', '(243)', 10, 10,
                    'Cm', 'Curium', '(247)', 11, 10,
                    'Bk', 'Berkelium', '(247)', 12, 10,
                    'Cf', 'Californium', '(251)', 13, 10,
                    'Es', 'Einstenium', '(252)', 14, 10,
                    'Fm', 'Fermium', '(257)', 15, 10,
                    'Md', 'Mendelevium', '(258)', 16, 10,
                    'No', 'Nobelium', '(259)', 17, 10,
                    'Lr', 'Lawrencium', '(262)', 18, 10,
                    'Rf', 'Rutherfordium', '(267)', 4, 7,
                    'Db', 'Dubnium', '(268)', 5, 7,
                    'Sg', 'Seaborgium', '(271)', 6, 7,
                    'Bh', 'Bohrium', '(272)', 7, 7,
                    'Hs', 'Hassium', '(270)', 8, 7,
                    'Mt', 'Meitnerium', '(276)', 9, 7,
                    'Ds', 'Darmstadium', '(281)', 10, 7,
                    'Rg', 'Roentgenium', '(280)', 11, 7,
                    'Cn', 'Copernicium', '(285)', 12, 7,
                    'Nh', 'Nihonium', '(286)', 13, 7,
                    'Fl', 'Flerovium', '(289)', 14, 7,
                    'Mc', 'Moscovium', '(290)', 15, 7,
                    'Lv', 'Livermorium', '(293)', 16, 7,
                    'Ts', 'Tennessine', '(294)', 17, 7,
                    'Og', 'Oganesson', '(294)', 18, 7
            ];

            let camera, scene, renderer;
            let controls;

            const objects = [];
            const targets = { table: [], sphere: [], helix: [], grid: [] };

            init();
            animate();

            function init() {

                    camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
                    camera.position.z = 3000;

                    scene = new THREE.Scene();

                    // table

                    for ( let i = 0; i < table.length; i += 5 ) {

                            // 元素外层div
                            const element = document.createElement( 'div' );
                            element.className = 'element';
                            element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';

                            // 原子序数
                            const number = document.createElement( 'div' );
                            number.className = 'number';
                            number.textContent = ( i / 5 ) + 1;
                            element.appendChild( number );

                            // 元素符号
                            const symbol = document.createElement( 'div' );
                            symbol.className = 'symbol';
                            symbol.textContent = table[ i ];
                            element.appendChild( symbol );

                            // 元素英文名称 + 原子量
                            const details = document.createElement( 'div' );
                            details.className = 'details';
                            details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
                            element.appendChild( details );

                            // 将元素DOM对象传给 CSS3DObject对象,随机生成位置
                            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 );

                            // 设置每个元素在table的具体位置
                            const object = new THREE.Object3D();
                            object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
                            object.position.y = - ( table[ i + 4 ] * 180 ) + 990;

                            targets.table.push( object );

                    }

                    // sphere

                    const vector = new THREE.Vector3();

                    for ( let i = 0, l = objects.length; i < l; i ++ ) {
                            // Math.acos: 反余弦值
                            const phi = Math.acos( - 1 + ( 2 * i ) / l );
                            const theta = Math.sqrt( l * Math.PI ) * phi; // Math.sqrt 返回一个数的平方根,Math.PI 表示一个圆的周长与直径的比例,约为 3.14159

                            // Object3D: 三维物体
                            const object = new THREE.Object3D();
                            // .setFromSphericalCoords ( radius : Float, phi : Float, theta : Float ) : this   从球坐标中的radius、phi和theta设置该向量。
                            // radius: 半径值
                            // phi: 与y(up)轴的极角(以弧度为单位)。默认值为0
                            // theta: 绕y(up)轴的赤道角(方位角)(以弧度为单位)。 默认值为 0。
                            object.position.setFromSphericalCoords( 800, phi, theta );

                            // .multiplyScalar ( s : Float ) : this 将该向量与所传入的标量s进行相乘
                            vector.copy( object.position ).multiplyScalar( 2 );

                            object.lookAt( vector );

                            targets.sphere.push( object );

                    }

                    // helix

                    for ( let i = 0, l = objects.length; i < l; i ++ ) {

                            const theta = i * 0.175 + Math.PI;
                            const y = - ( i * 8 ) + 450;

                            const object = new THREE.Object3D();

                            // .setFromCylindricalCoords ( radius : Float, theta : Float, y : Float )   从圆柱坐标中的radius、theta和y设置该向量。
                            // radius - 从原点到x-z平面上一点的距离 默认值为 1.0.
                            // theta - 在x-z平面内的逆时针角度,以z轴正方向的计算弧度。默认值为0。
                            // y - x-z平面以上的高度 默认值为 0.
                            object.position.setFromCylindricalCoords( 900, theta, y );

                            vector.x = object.position.x * 2;
                            vector.y = object.position.y;
                            vector.z = object.position.z * 2;

                            object.lookAt( vector );

                            targets.helix.push( object );

                    }

                    // grid

                    for ( let i = 0; i < objects.length; i ++ ) {

                            const object = new THREE.Object3D();

                            // x 值: -800  -400 0 400 800
                            object.position.x = ( ( i % 5 ) * 400 ) - 800; 
                            // y轴也是排列5个
                            object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
                            // 注元素一共118个,则 Math.floor( i / 25 ) 值为 0 - 4, 则z轴排5个
                            object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;

                            targets.grid.push( object );

                    }

                    // CSS3DRenderer 渲染器
                    renderer = new CSS3DRenderer();
                    renderer.setSize( window.innerWidth, window.innerHeight );
                    document.getElementById( 'container' ).appendChild( renderer.domElement );

                    // TrackballControls 轨迹球控件, 注意区分他和 OrbitControls 的区别
                    controls = new TrackballControls( camera, renderer.domElement );
                    controls.minDistance = 500;
                    controls.maxDistance = 6000;
                    controls.addEventListener( 'change', render );

                    const buttonTable = document.getElementById( 'table' );
                    buttonTable.addEventListener( 'click', function () {

                            transform( targets.table, 2000 );

                    } );

                    const buttonSphere = document.getElementById( 'sphere' );
                    buttonSphere.addEventListener( 'click', function () {

                            transform( targets.sphere, 2000 );

                    } );

                    const buttonHelix = document.getElementById( 'helix' );
                    buttonHelix.addEventListener( 'click', function () {

                            transform( targets.helix, 2000 );

                    } );

                    const buttonGrid = document.getElementById( 'grid' );
                    buttonGrid.addEventListener( 'click', function () {

                            transform( targets.grid, 2000 );

                    } );

                    transform( targets.table, 2000 );

                    //

                    window.addEventListener( 'resize', onWindowResize );

            }

            // 变换动画
            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 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 );

            }

    </script>
</body>
</html>
相关推荐
码爸13 分钟前
flink doris批量sink
java·前端·flink
深情废杨杨15 分钟前
前端vue-父传子
前端·javascript·vue.js
J不A秃V头A1 小时前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂2 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客2 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹2 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
天下无贼!4 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr4 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林4 小时前
npm发布插件超级简单版
前端·npm·node.js