Three.js Tutorial: How to make a periodic table
Hi.
I just want to make the periodic table example in three.js documentations step by step.
First of all, what is three.js?
Three.js is a library for easy use of WebGL, rendering objects inside our browser. you can read a lot about it with a little search inside Google.
You can see the example that we want to make here:
https://threejs.org/examples/css3d_periodictable.html
Let’s get into it.
Required files, folders, libraries
First of all, we need to make the required files, folders and download our required libraries.
Files:
1- index.html
2- main.js
Folders:
1- lib
2- lib/controls
3- lib/renderers
Libraries:
1- Download the main three js file:
https://github.com/mrdoob/three.js/raw/master/build/three.min.js
Then copy it inside lib folder
2- Download tween library file:
https://github.com/mrdoob/three.js/raw/master/examples/js/libs/tween.min.js
Then copy it inside lib folder
3- Download TrackballControls file:
https://raw.githubusercontent.com/mrdoob/three.js/master/examples/js/controls/TrackballControls.js
Then copy it inside lib/controls
4 — Download CSS3DRenderer file:
https://github.com/mrdoob/three.js/raw/master/examples/js/renderers/CSS3DRenderer.js
Then copy it inside lib/controls
Initiating files and environment
1- index.html
This is our HTML part which is pretty simple and small.
This part just connects our javascript codes to HTML and loads the libraries inside the lib folder into the project and makes a couple of buttons for controlling the table
So just copy and paste these lines of code inside index.html:
<!DOCTYPE html>
<html>
<head>
<title>Three.js Periodic Table Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
html, body {
height: 100%;
}body {
background-color: #000000;
margin: 0;
font-family: Helvetica, sans-serif;;
overflow: hidden;
}a {
color: #ffffff;
}#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);
text-align: center;
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><script src="./lib/three.js"></script>
<script src="./lib/Tween.min.js"></script>
<script src="./lib/controls/TrackballControls.js"></script>
<script src="./lib/renderers/CSS3DRenderer.js"></script><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 src="./main.js"></script>
</body>
</html>
Three.js stuff will be loaded inside this tag:
<div id="container"></div>
If we open the index.html in our browser we will see a black screen with 4 neon buttons something like this:
2- main.js
In the main.js we will add
Periodic table array for a sample data
Init function to initiate the camera, scene, objects and some other stuff inside it
The animate function which is actually a loop to render our project in 60 fps and we will be able to see the changes.
So our file looks like this:
var 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
];
init();
animate();function init() {
}function animate() {
}
So at the end of this step, we are ready to do the main job.
Camera, Scene, Renderer
We need to initiate basic three.js tools to be able to see the things inside our window.
1- Camera
The camera is actually our eyes inside three.js environment without a camera we can’t see anything.
So, add the camera variable after the table variable to be able to use it in other functions:
var camera;
Then make a new instance of a perspective camera inside of init function:
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
A perspective camera receives 5 parameters (field of view, aspect ratio, near, far)
Read more about it in three.js docs:
When we make an object inside three.js the object will be created at coordination 0,0,0 so with our camera we can’t see anything.
To see the objects we need to give it a position which is z = 3000 in our case.
So add this line just under camera initialization inside init function.
So our init function would look like this.
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 3000;
2- Scene
The scene actually is a space which we can add the objects and camera into it.
Add a scene variable after the camera variable to use it inside other functions something like this:
var camera,scene;
Then add this line to the init function to get a new instance of the scene class:
scene = new THREE.Scene();
Now our main.js file looks like this:
var table = [
// just the Table array at the first of article
];var camera,scene;init();
animate();function init() {
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 3000;scene = new THREE.Scene();}function animate() {
}
3- Renderer
Renderer loads camera and the scene inside of our HTML tag.
Which is in our case, a CSS3DRenderer you can read about it inside this link:
We need to add a renderer variable after camera and scene variables to be able to use it inside the other functions:
var camera,scene,renderer;
Then we need to add these lines to the init function:
renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );
In the first line, we get a new instance of CSS3DRenderer class.
The second line, we set a size for it.
In the third line, we append the renderer to html tag with the ‘container’ id.
Then we need to add a function named render to load our scene and camera inside the renderer:
function render() {renderer.render( scene, camera );}
Now we need to add a function named onWindowResize:
function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );render();}
This function will be called every time that window size changes.
Then we need to specify the renderer and camera size, so when we change the window size the renderer size will be changed to stay fit inside the window.
Also, this function will be called at the first run using this line:
window.addEventListener( 'resize', onWindowResize, false );
Add this line at the end of the init function.
So it will call the render function.
Now add this line inside of the animate function to get our project animated instead of a static image:
requestAnimationFrame( animate );
Now our main.js file looks like this:
var table = [
// just the Table array at the first of article
];var camera,scene,renderer;init();
animate();function init() {
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 3000;scene = new THREE.Scene();renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );window.addEventListener( 'resize', onWindowResize, false );}function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );render();}function render() {renderer.render( scene, camera );}function animate() {
requestAnimationFrame( animate );
}
In this step, we added three basic elements of three.js inside our main.js file.
Now our scene is empty and we still can’t see anything but a black screen with 4 buttons.
So let’s add objects inside our scene.
Adding Objects
In our case, we need to convert periodic table elements in the variable to objects then add them inside an array called objects with random positions.
Then we will add them inside the table field inside of the targets variable with their right positions inside the periodic table.
First, we will add the required variables after camera, scene, renderer variables to use them inside functions:
var objects = [];
var targets = { table: [], sphere: [], helix: [], grid: [] };
Then make a function named “simpleObjectsLayout”:
function simpleObjectsLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';var number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );var symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );var details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details );var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
}
This function does a loop inside the table variable to convert array elements into HTML elements then adds them as objects using CSS3DObject function, to the scene and pushes them in the objects array to use them for rendering other layouts.
Call the function inside init function after renderer lines:
simpleObjectsLayout();
Now our main.js file looks like this:
var table = [
// just the Table array at the first of article
];var camera,scene,renderer;var objects = [];
var 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();simpleObjectsLayout();renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );window.addEventListener( 'resize', onWindowResize, false );}function simpleObjectsLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';var number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );var symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );var details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details );var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
}function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );render();}function render() {renderer.render( scene, camera );}function animate() {
requestAnimationFrame( animate );}
So let’s add table layout.
function tableLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;targets.table.push( object );}
}
Add this function in main.js.
This function just adds objects inside the table element of targets object with their standard position.
Then call this function after simpleObjectsLayout function inside the init function.
At the end of this step our main.js file looks like this:
var table = [
// just the Table array at the first of article
];var camera,scene,renderer;var objects = [];
var 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();simpleObjectsLayout();
tableLayout();renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );window.addEventListener( 'resize', onWindowResize, false );}function simpleObjectsLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';var number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );var symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );var details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details );var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
}function tableLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;targets.table.push( object );}
}function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );render();}function render() {renderer.render( scene, camera );}function animate() {
requestAnimationFrame( animate );
}
But still, we can’t see any changes and the tutorial gets boring.
So let’s add animations in the project to see what happens.
Tween
In this step, we will add animations inside our project to animate our objects.
For this purpose, we will use tween.
Add this function to the main.js
function transform( targets, duration ) {TWEEN.removeAll();for ( var i = 0; i < objects.length; i ++ ) {var object = objects[ i ];
var 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();}
This function creates a tween for each one of the objects then transforms them into corresponding target location and rotation.
The first line completely empties the internal list of tweens.
Then add this line inside the animate function:
TWEEN.update();
Then call the transform function inside the init function before window.eventListener:
transform( targets.table, 2000 );
Now main.js file looks like this:
var table = [
// just the Table array at the first of article
];var camera,scene,renderer;var objects = [];
var 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();simpleObjectsLayout();
tableLayout();renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );transform( targets.table, 2000 );window.addEventListener( 'resize', onWindowResize, false );}function simpleObjectsLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';var number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );var symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );var details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details );var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
}function tableLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;targets.table.push( object );}
}function transform( targets, duration ) {TWEEN.removeAll();for ( var i = 0; i < objects.length; i ++ ) {var object = objects[ i ];
var 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 render() {renderer.render( scene, camera );}function animate() {
requestAnimationFrame( animate );
TWEEN.update();
}
After saving the main.js file, Open index.html inside your browser or refresh it.
You will be able to see beautiful animation from transforming the positions of the table elements.
But none of the buttons works so let’s get them working.
Adding other layouts
For this step, we need to make other layouts for the project
So similar to the adding objects step we will ad sphere, helix, grid layouts:
function sphereLayout() {
var vector = new THREE.Vector3();for ( var i = 0, l = objects.length; i < l; i ++ ) {var phi = Math.acos( - 1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;var object = new THREE.Object3D();object.position.setFromSphericalCoords( 800, phi, theta );vector.copy( object.position ).multiplyScalar( 2 );object.lookAt( vector );targets.sphere.push( object );}}function helixLayout() {
var vector = new THREE.Vector3();for ( var i = 0, l = objects.length; i < l; i ++ ) {var theta = i * 0.175 + Math.PI;
var y = - ( i * 8 ) + 450;var object = new THREE.Object3D();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 );}}function gridLayout() {
for ( var i = 0; i < objects.length; i ++ ) {var object = new THREE.Object3D();object.position.x = ( ( i % 5 ) * 400 ) - 800;
object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;targets.grid.push( object );}}
These three functions just making loops inside objects array and adding them with different positions by target types inside the target function’s relevant element.
Then call the functions inside the init function so the file looks like this:
var table = [
// just the Table array at the first of article
];var camera,scene,renderer;var objects = [];
var 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();simpleObjectsLayout();
tableLayout();
sphereLayout();
helixLayout();
gridLayout();renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );transform( targets.table, 2000 );window.addEventListener( 'resize', onWindowResize, false );}function simpleObjectsLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';var number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );var symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );var details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details );var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
}function tableLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;targets.table.push( object );}
}function sphereLayout() {
var vector = new THREE.Vector3();for ( var i = 0, l = objects.length; i < l; i ++ ) {var phi = Math.acos( - 1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;var object = new THREE.Object3D();object.position.setFromSphericalCoords( 800, phi, theta );vector.copy( object.position ).multiplyScalar( 2 );object.lookAt( vector );targets.sphere.push( object );}}function helixLayout() {
var vector = new THREE.Vector3();for ( var i = 0, l = objects.length; i < l; i ++ ) {var theta = i * 0.175 + Math.PI;
var y = - ( i * 8 ) + 450;var object = new THREE.Object3D();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 );}}function gridLayout() {
for ( var i = 0; i < objects.length; i ++ ) {var object = new THREE.Object3D();object.position.x = ( ( i % 5 ) * 400 ) - 800;
object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;targets.grid.push( object );}}function transform( targets, duration ) {TWEEN.removeAll();for ( var i = 0; i < objects.length; i ++ ) {var object = objects[ i ];
var 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 render() {renderer.render( scene, camera );}function animate() {
requestAnimationFrame( animate );
TWEEN.update();
}
Now, we are ready to use them.
Buttons
Now by having all of the required layouts we can use the buttons to transform between them
So we need to set a click listener for the buttons
For this purpose I made a function:
function addClickListener(target,elementId){
var button = document.getElementById( elementId );
button.addEventListener( 'click', function () {transform( target, 2000 );}, false );
}
This function adds a click listener to an element by its id and inside the callback function, uses the target parameter to pass it into the transform function.
Then use it inside of init function like:
addClickListener(targets.table,'table');
addClickListener(targets.sphere,'sphere');
addClickListener(targets.helix, 'helix');
addClickListener(targets.grid,'grid');
Just after calling “gridLayout” function.
Now our file looks like:
var table = [
// just the Table array at the first of article
];var camera,scene,renderer;
var objects = [];
var 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();simpleObjectsLayout();
tableLayout();
sphereLayout();
helixLayout();
gridLayout();
addClickListener(targets.table,'table');
addClickListener(targets.sphere,'sphere');
addClickListener(targets.helix, 'helix');
addClickListener(targets.grid,'grid');renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );transform( targets.table, 2000 );window.addEventListener( 'resize', onWindowResize, false );}function simpleObjectsLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';var number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );var symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );var details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details );var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
}function tableLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;targets.table.push( object );}
}function addClickListener(target,elementId){
var button = document.getElementById( elementId );
button.addEventListener( 'click', function () {transform( target, 2000 );}, false );
}function sphereLayout() {
var vector = new THREE.Vector3();for ( var i = 0, l = objects.length; i < l; i ++ ) {var phi = Math.acos( - 1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;var object = new THREE.Object3D();object.position.setFromSphericalCoords( 800, phi, theta );vector.copy( object.position ).multiplyScalar( 2 );object.lookAt( vector );targets.sphere.push( object );}}function helixLayout() {
var vector = new THREE.Vector3();for ( var i = 0, l = objects.length; i < l; i ++ ) {var theta = i * 0.175 + Math.PI;
var y = - ( i * 8 ) + 450;var object = new THREE.Object3D();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 );}}function gridLayout() {
for ( var i = 0; i < objects.length; i ++ ) {var object = new THREE.Object3D();object.position.x = ( ( i % 5 ) * 400 ) - 800;
object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;targets.grid.push( object );}}function transform( targets, duration ) {TWEEN.removeAll();for ( var i = 0; i < objects.length; i ++ ) {var object = objects[ i ];
var 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 render() {renderer.render( scene, camera );}function animate() {
requestAnimationFrame( animate );
TWEEN.update();
}
And we can use the buttons.
But we can get it a little fancier.
Trackball Controls
After this step, we will be able to control the camera using trackball and mouse.
Add a controls variable after targets variable:
var controls;
Then add this function to the file:
function initTrackbarControls() {
controls = new THREE.TrackballControls( camera, renderer.domElement );
controls.rotateSpeed = 0.5;
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener( 'change', render );
}
This function initiates trackbar controls for letting us to control the camera using the mouse.
Then call it inside init function after ‘document.getElementById’ line:
initTrackbarControls();
add this line inside the animate function:
controls.update();
Now our file looks like this:
var 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
];var camera,scene,renderer;
var objects = [];
var targets = { table: [], sphere: [], helix: [], grid: [] };
var controls;init();
animate();function init() {
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 3000;scene = new THREE.Scene();simpleObjectsLayout();
tableLayout();
sphereLayout();
helixLayout();
gridLayout();
addClickListener(targets.table,'table');
addClickListener(targets.sphere,'sphere');
addClickListener(targets.helix, 'helix');
addClickListener(targets.grid,'grid');renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );initTrackbarControls();transform( targets.table, 2000 );window.addEventListener( 'resize', onWindowResize, false );}function simpleObjectsLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';var number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );var symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );var details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
element.appendChild( details );var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
}function tableLayout() {
for ( var i = 0; i < table.length; i += 5 ) {var object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;targets.table.push( object );}
}function addClickListener(target,elementId){
var button = document.getElementById( elementId );
button.addEventListener( 'click', function () {transform( target, 2000 );}, false );
}function initTrackbarControls() {
controls = new THREE.TrackballControls( camera, renderer.domElement );
controls.rotateSpeed = 0.5;
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener( 'change', render );
}function sphereLayout() {
var vector = new THREE.Vector3();for ( var i = 0, l = objects.length; i < l; i ++ ) {var phi = Math.acos( - 1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;var object = new THREE.Object3D();object.position.setFromSphericalCoords( 800, phi, theta );vector.copy( object.position ).multiplyScalar( 2 );object.lookAt( vector );targets.sphere.push( object );}}function helixLayout() {
var vector = new THREE.Vector3();for ( var i = 0, l = objects.length; i < l; i ++ ) {var theta = i * 0.175 + Math.PI;
var y = - ( i * 8 ) + 450;var object = new THREE.Object3D();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 );}}function gridLayout() {
for ( var i = 0; i < objects.length; i ++ ) {var object = new THREE.Object3D();object.position.x = ( ( i % 5 ) * 400 ) - 800;
object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;targets.grid.push( object );}}function transform( targets, duration ) {TWEEN.removeAll();for ( var i = 0; i < objects.length; i ++ ) {var object = objects[ i ];
var 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 render() {renderer.render( scene, camera );}function animate() {
requestAnimationFrame( animate );
TWEEN.update();controls.update();}
At the end of this step, we finished the project.
But Let’s do some refactoring.
Refactoring
In this step, I will refactor the code for having a more performance and readable and maintainable code.
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, controls;
let targets = {simple: [], table: [], sphere: [], helix: [], grid: []};
init();
animate();
function init() {
initCamera();
initScene();
initObjects();
addClickListeners();
initRenderer();
initTrackbarControls();
transform(targets.table, 2000);
window.addEventListener('resize', onWindowResize, false);
}
function initCamera() {
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 3000;
}
function initScene() {
scene = new THREE.Scene();
}
function initRenderer() {
renderer = new THREE.CSS3DRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
}
function initObjects() {
simpleObjectsLayout();
generateGeometricLayouts();
}
function addClickListeners() {
addClickListener(targets.table, 'table');
addClickListener(targets.sphere, 'sphere');
addClickListener(targets.helix, 'helix');
addClickListener(targets.grid, 'grid');
}
function simpleObjectsLayout() {
for (let i = 0; i < table.length; i += 5) {
let object = new THREE.CSS3DObject(htmlElement(table, i));
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add(object);
targets.simple.push(object);
tableLayout(table, i);
}
}
function htmlElement(table, i) {
let element = document.createElement('div');
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + (Math.random() * 0.5 + 0.25) + ')';
let number = document.createElement('div');
number.className = 'number';
number.textContent = (i / 5) + 1;
element.appendChild(number);
let symbol = document.createElement('div');
symbol.className = 'symbol';
symbol.textContent = table[i];
element.appendChild(symbol);
let details = document.createElement('div');
details.className = 'details';
details.innerHTML = table[i + 1] + '<br>' + table[i + 2];
element.appendChild(details);
return element;
}
function tableLayout(table, index) {
let object = new THREE.Object3D();
object.position.x = (table[index + 3] * 140) - 1330;
object.position.y = -(table[index + 4] * 180) + 990;
targets.table.push(object);
}
function addClickListener(target, elementId) {
const button = document.getElementById(elementId);
button.addEventListener('click', function () {
transform(target, 2000);
}, false);
}
function initTrackbarControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 0.5;
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener('change', render);
}
function generateGeometricLayouts() {
let sphereVector = new THREE.Vector3();
let helixVector = new THREE.Vector3();
for (let i = 0, l = targets.simple.length; i < l; i++) {
addSphereObject(sphereVector, i, l);
addHelixObject(helixVector, i);
addGridObject(i);
}
}
function addSphereObject(sphereVector, index, length) {
const phi = Math.acos(-1 + (2 * index) / length);
const theta = Math.sqrt(length * Math.PI) * phi;
let object = new THREE.Object3D();
object.position.setFromSphericalCoords(800, phi, theta);
sphereVector.copy(object.position).multiplyScalar(2);
object.lookAt(sphereVector);
targets.sphere.push(object);
}
function addHelixObject(helixVector, index) {
const theta = index * 0.175 + Math.PI;
const y = -(index * 8) + 450;
let object = new THREE.Object3D();
object.position.setFromCylindricalCoords(900, theta, y);
helixVector.x = object.position.x * 2;
helixVector.y = object.position.y;
helixVector.z = object.position.z * 2;
object.lookAt(helixVector);
targets.helix.push(object);
}
function addGridObject(index) {
let object = new THREE.Object3D();
object.position.x = ((index % 5) * 400) - 800;
object.position.y = (-(Math.floor(index / 5) % 5) * 400) + 800;
object.position.z = (Math.floor(index / 25)) * 1000 - 2000;
targets.grid.push(object);
}
function transform(target, duration) {
TWEEN.removeAll();
for (let i = 0; i < targets.simple.length; i++) {
let object = targets.simple[i];
let targetObject = target[i];
transformObjectPosition(object, targetObject, duration);
transformObjectRotation(object, targetObject, duration);
}
new TWEEN.Tween(this)
.to({}, duration * 2)
.onUpdate(render)
.start();
}
function transformObjectPosition(object, targetObject, duration) {
new TWEEN.Tween(object.position)
.to({
x: targetObject.position.x,
y: targetObject.position.y,
z: targetObject.position.z
}, Math.random() * duration + duration)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
function transformObjectRotation(object, targetObject, duration) {
new TWEEN.Tween(object.rotation)
.to({
x: targetObject.rotation.x,
y: targetObject.rotation.y,
z: targetObject.rotation.z
}, Math.random() * duration + duration)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
controls.update();
}
After some refactoring, my main.js file looks like above.
Update: Clickable elements
I decided to add elements click handling in this tutorial.
Now in your refactored code, add this line in the htmlElement function before return element:
element.addEventListener('click', ()=>elementClickHandler(i), false);
Then add this function to the project:
function elementClickHandler(i){
transform(targets.table,1000);
new TWEEN.Tween(targets.simple[i / 5].position)
.to({
x: 0,
y: 0,
z: 2500
}, Math.random() * 2000 + 2000)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
new TWEEN.Tween(this)
.to({}, 2000 * 2)
.onUpdate(render)
.start();
}
Final main.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, controls, composer;
var hblur, vblur;
let targets = {simple: [], table: [], sphere: [], helix: [], grid: []};
init();
animate();
function init() {
initCamera();
initScene();
initObjects();
addClickListeners();
initRenderer();
initTrackbarControls();
transform(targets.table, 2000);
window.addEventListener('resize', onWindowResize, false);
}
function initCamera() {
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 3000;
}
function initScene() {
scene = new THREE.Scene();
}
function initRenderer() {
renderer = new THREE.CSS3DRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
}
function initObjects() {
simpleObjectsLayout();
generateGeometricLayouts();
}
function addClickListeners() {
addClickListener(targets.table, 'table');
addClickListener(targets.sphere, 'sphere');
addClickListener(targets.helix, 'helix');
addClickListener(targets.grid, 'grid');
}
function simpleObjectsLayout() {
for (let i = 0; i < table.length; i += 5) {
let object = new THREE.CSS3DObject(htmlElement(table, i));
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add(object);
targets.simple.push(object);
tableLayout(table, i);
}
}
function htmlElement(table, i) {
let element = document.createElement('div');
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + (Math.random() * 0.5 + 0.25) + ')';
let number = document.createElement('div');
number.className = 'number';
number.textContent = (i / 5) + 1;
element.appendChild(number);
let symbol = document.createElement('div');
symbol.className = 'symbol';
symbol.textContent = table[i];
element.appendChild(symbol);
let details = document.createElement('div');
details.className = 'details';
details.innerHTML = table[i + 1] + '<br>' + table[i + 2];
element.appendChild(details);
element.addEventListener('click', ()=>elementClickHandler(i), false);
return element;
}
function elementClickHandler(i){
transform(targets.table,1000);
new TWEEN.Tween(targets.simple[i / 5].position)
.to({
x: 0,
y: 0,
z: 2500
}, Math.random() * 2000 + 2000)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
new TWEEN.Tween(this)
.to({}, 2000 * 2)
.onUpdate(render)
.start();
}
function tableLayout(table, index) {
let object = new THREE.Object3D();
object.position.x = (table[index + 3] * 140) - 1330;
object.position.y = -(table[index + 4] * 180) + 990;
targets.table.push(object);
}
function addClickListener(target, elementId) {
const button = document.getElementById(elementId);
button.addEventListener('click', function () {
transform(target, 2000);
}, false);
}
function initTrackbarControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 0.5;
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener('change', render);
}
function generateGeometricLayouts() {
let sphereVector = new THREE.Vector3();
let helixVector = new THREE.Vector3();
for (let i = 0, l = targets.simple.length; i < l; i++) {
addSphereObject(sphereVector, i, l);
addHelixObject(helixVector, i);
addGridObject(i);
}
}
function addSphereObject(sphereVector, index, length) {
const phi = Math.acos(-1 + (2 * index) / length);
const theta = Math.sqrt(length * Math.PI) * phi;
let object = new THREE.Object3D();
object.position.setFromSphericalCoords(800, phi, theta);
sphereVector.copy(object.position).multiplyScalar(2);
object.lookAt(sphereVector);
targets.sphere.push(object);
}
function addHelixObject(helixVector, index) {
const theta = index * 0.175 + Math.PI;
const y = -(index * 8) + 450;
let object = new THREE.Object3D();
object.position.setFromCylindricalCoords(900, theta, y);
helixVector.x = object.position.x * 2;
helixVector.y = object.position.y;
helixVector.z = object.position.z * 2;
object.lookAt(helixVector);
targets.helix.push(object);
}
function addGridObject(index) {
let object = new THREE.Object3D();
object.position.x = ((index % 5) * 400) - 800;
object.position.y = (-(Math.floor(index / 5) % 5) * 400) + 800;
object.position.z = (Math.floor(index / 25)) * 1000 - 2000;
targets.grid.push(object);
}
function transform(target, duration) {
TWEEN.removeAll();
for (let i = 0; i < targets.simple.length; i++) {
let object = targets.simple[i];
let targetObject = target[i];
transformObjectPosition(object, targetObject, duration);
transformObjectRotation(object, targetObject, duration);
}
new TWEEN.Tween(this)
.to({}, duration * 2)
.onUpdate(render)
.start();
}
function transformObjectPosition(object, targetObject, duration) {
new TWEEN.Tween(object.position)
.to({
x: targetObject.position.x,
y: targetObject.position.y,
z: targetObject.position.z
}, Math.random() * duration + duration)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
function transformObjectRotation(object, targetObject, duration) {
new TWEEN.Tween(object.rotation)
.to({
x: targetObject.rotation.x,
y: targetObject.rotation.y,
z: targetObject.rotation.z
}, Math.random() * duration + duration)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
controls.update();
composer.render();
}
Final source: