元素周期表
2026-03-13
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Anime.js Periodic Table Auto Layout animation</title>
<style>
@font-face {
font-family: "IoskeleyMono";
src: url("https://assets.codepen.io/1137/IoskeleyMono-Regular.woff2")
format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IoskeleyMono";
src: url("https://assets.codepen.io/1137/IoskeleyMono-Bold.woff2")
format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
:root {
--black-1: #252423;
--black-2: #262422;
--black-3: #262422;
--black-4: #262422;
--black-5: #272421;
--black-6: #272421;
--white-1: #f6f4f2;
--white-2: #b8b6b3;
--white-3: #9a9593;
--white-4: #524f49;
--white-5: #413d39;
--white-6: #312e2b;
--red-1: #ff4b4b;
--red-2: #bf3e3e;
--red-3: #9e3838;
--red-4: #532a29;
--red-5: #412726;
--red-6: #322523;
--corail-1: #ff8333;
--corail-2: #bf672d;
--corail-3: #9e582b;
--corail-4: #533724;
--corail-5: #412f23;
--corail-6: #322922;
--orange-1: #ffa828;
--orange-2: #bf8026;
--orange-3: #9e6d25;
--orange-4: #523e23;
--orange-5: #413422;
--orange-6: #322b21;
--yellow-1: #ffcc2a;
--yellow-2: #bf9a27;
--yellow-3: #9e8026;
--yellow-4: #524723;
--yellow-5: #413922;
--yellow-6: #322d21;
--citrus-1: #f9f640;
--citrus-2: #bab836;
--citrus-3: #9b9932;
--citrus-4: #515027;
--citrus-5: #403f24;
--citrus-6: #323022;
--lime-1: #b7ff54;
--lime-2: #8bbe44;
--lime-3: #759d3d;
--lime-4: #42522b;
--lime-5: #384027;
--lime-6: #2d3123;
--green-1: #6aff65;
--green-2: #54be50;
--green-3: #4a9e45;
--green-4: #31522e;
--green-5: #2c4029;
--green-6: #273124;
--emerald-1: #57f695;
--emerald-2: #47b873;
--emerald-3: #409962;
--emerald-4: #2d5039;
--emerald-5: #293f30;
--emerald-6: #263128;
--turquoise-1: #66ffbc;
--turquoise-2: #52be8e;
--turquoise-3: #479e77;
--turquoise-4: #305242;
--turquoise-5: #2b4035;
--turquoise-6: #26312b;
--cyan-1: #26f2d5;
--cyan-2: #25b5a0;
--cyan-3: #259686;
--cyan-4: #244e48;
--cyan-5: #233f39;
--cyan-6: #23302d;
--sega-1: #05dbe9;
--sega-2: #0fa4ae;
--sega-3: #138990;
--sega-4: #1e4a4c;
--sega-5: #203b3c;
--sega-6: #212f2f;
--sky-1: #33b3f1;
--sky-2: #2e88b4;
--sky-3: #2c7395;
--sky-4: #26424e;
--sky-5: #25363e;
--sky-6: #242c2f;
--indigo-1: #717aff;
--indigo-2: #595fbe;
--indigo-3: #4d519e;
--indigo-4: #323351;
--indigo-5: #2c2c3f;
--indigo-6: #282630;
--lavender-1: #a369ff;
--lavender-2: #7d53be;
--lavender-3: #6a489e;
--lavender-4: #3e3051;
--lavender-5: #342a3f;
--lavender-6: #2b2530;
--purple-1: #c06ddf;
--purple-2: #9356a8;
--purple-3: #7b4a8c;
--purple-4: #45314b;
--purple-5: #392b3c;
--purple-6: #2f262d;
--magenta-1: #e962bf;
--magenta-2: #af4e90;
--magenta-3: #93447a;
--magenta-4: #4e2e43;
--magenta-5: #3f2936;
--magenta-6: #31252b;
--pink-1: #ff86a7;
--pink-2: #bf687f;
--pink-3: #9f586b;
--pink-4: #53363c;
--pink-5: #412e32;
--pink-6: #322729;
--bg-1: #252423;
--bg-2: #2a2928;
--bg-3: #2f2e2d;
--bg-4: #353433;
--bg-5: #3a3938;
--fg-1: #dddcda;
--fg-2: #c6c3c1;
--fg-3: #96918f;
--fg-4: #65655e;
--fg-5: #33332e;
--input-border-radius: 0.25rem;
--br: 1rem;
--padding: 1rem;
--border-width: 1px;
}
[data-color="fg"] {
--color-1: var(--fg-1);
--color-2: var(--fg-2);
--color-3: var(--fg-3);
--color-4: var(--fg-4);
--color-5: var(--fg-5);
}
[data-color="bg"] {
--color-1: var(--bg-1);
--color-2: var(--bg-2);
--color-3: var(--bg-3);
--color-4: var(--bg-4);
--color-5: var(--bg-5);
}
[data-color="0"] {
--color-1: var(--red-1);
--color-2: var(--red-2);
--color-3: var(--red-3);
--color-4: var(--red-4);
--color-5: var(--red-5);
}
[data-color="1"] {
--color-1: var(--corail-1);
--color-2: var(--corail-2);
--color-3: var(--corail-3);
--color-4: var(--corail-4);
--color-5: var(--corail-5);
}
[data-color="2"] {
--color-1: var(--orange-1);
--color-2: var(--orange-2);
--color-3: var(--orange-3);
--color-4: var(--orange-4);
--color-5: var(--orange-5);
}
[data-color="3"] {
--color-1: var(--yellow-1);
--color-2: var(--yellow-2);
--color-3: var(--yellow-3);
--color-4: var(--yellow-4);
--color-5: var(--yellow-5);
}
[data-color="4"] {
--color-1: var(--citrus-1);
--color-2: var(--citrus-2);
--color-3: var(--citrus-3);
--color-4: var(--citrus-4);
--color-5: var(--citrus-5);
}
[data-color="5"] {
--color-1: var(--lime-1);
--color-2: var(--lime-2);
--color-3: var(--lime-3);
--color-4: var(--lime-4);
--color-5: var(--lime-5);
}
[data-color="6"] {
--color-1: var(--green-1);
--color-2: var(--green-2);
--color-3: var(--green-3);
--color-4: var(--green-4);
--color-5: var(--green-5);
}
[data-color="7"] {
--color-1: var(--emerald-1);
--color-2: var(--emerald-2);
--color-3: var(--emerald-3);
--color-4: var(--emerald-4);
--color-5: var(--emerald-5);
}
[data-color="8"] {
--color-1: var(--turquoise-1);
--color-2: var(--turquoise-2);
--color-3: var(--turquoise-3);
--color-4: var(--turquoise-4);
--color-5: var(--turquoise-5);
}
[data-color="9"] {
--color-1: var(--cyan-1);
--color-2: var(--cyan-2);
--color-3: var(--cyan-3);
--color-4: var(--cyan-4);
--color-5: var(--cyan-5);
}
[data-color="10"] {
--color-1: var(--sega-1);
--color-2: var(--sega-2);
--color-3: var(--sega-3);
--color-4: var(--sega-4);
--color-5: var(--sega-5);
}
[data-color="11"] {
--color-1: var(--sky-1);
--color-2: var(--sky-2);
--color-3: var(--sky-3);
--color-4: var(--sky-4);
--color-5: var(--sky-5);
}
[data-color="12"] {
--color-1: var(--indigo-1);
--color-2: var(--indigo-2);
--color-3: var(--indigo-3);
--color-4: var(--indigo-4);
--color-5: var(--indigo-5);
}
[data-color="13"] {
--color-1: var(--lavender-1);
--color-2: var(--lavender-2);
--color-3: var(--lavender-3);
--color-4: var(--lavender-4);
--color-5: var(--lavender-5);
}
*,
*:before,
*:after {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0;
font: inherit;
vertical-align: baseline;
}
html,
body {
background-color: var(--bg-1);
color: var(--fg-1);
font-family: "IoskeleyMono", monospace, sans-serif;
}
.br {
border-radius: var(--br);
}
.br .br {
border-radius: calc(var(--br) * 0.5);
}
.br .br .br {
border-radius: calc(var(--br) * 0.25);
}
.black {
color: var(--bg-1);
}
.white {
color: var(--fg-1);
}
.red {
color: var(--red-1);
}
.orange {
color: var(--corail-1);
}
.yellow {
color: var(--yellow-1);
}
.blue {
color: var(--sky-1);
}
.green {
color: var(--green-1);
}
.grid {
--one-cell: 100px;
--border-color: rgba(255, 255, 255, 0.75);
flex-grow: 1;
position: relative;
width: 100%;
height: 100%;
padding: 0;
}
.grid:before {
content: "";
pointer-events: none;
position: absolute;
z-index: 0;
left: 0;
top: 0;
width: 100%;
height: 100%;
background:
linear-gradient(
-90deg,
rgba(255, 255, 255, 0.05) 1px,
transparent 1px
),
linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
linear-gradient(
-90deg,
rgba(255, 255, 255, 0.04) 1px,
transparent 1px
),
linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px);
background-size:
calc(var(--one-cell) / 10) calc(var(--one-cell) / 10),
calc(var(--one-cell) / 10) calc(var(--one-cell) / 10),
var(--one-cell) var(--one-cell),
var(--one-cell) var(--one-cell),
var(--one-cell) var(--one-cell),
var(--one-cell) var(--one-cell);
background-position: 0 -1px;
backface-visibility: hidden;
}
.grid:before {
width: calc(75px + 100%);
left: -75px;
}
@media (min-width: 500px) {
.grid:before {
width: 100%;
left: 0;
}
}
/* Logs */
.log {
--width: 22ch;
--height: 20ch;
position: fixed;
z-index: 10;
top: 0;
overflow-y: scroll;
width: var(--width);
height: var(--height);
padding-left: 1ch;
padding-right: 1ch;
padding-bottom: 1ch;
font-family: ui-monospace, monospace;
white-space: pre;
background-color: rgba(0, 0, 0, 0.5);
}
@keyframes reveal {
from {
opacity: 1;
}
to {
opacity: 0.4;
}
}
.log span {
animation: reveal 2s ease-in-out forwards;
}
/* Inputs */
button,
select {
width: 100%;
flex-shrink: 0.5;
border: none;
background-color: var(--white-1);
border-radius: var(--input-border-radius);
white-space: nowrap;
margin: 0;
height: 2rem;
}
button {
padding: 0 0.75rem;
}
select:disabled,
button:disabled {
background-color: var(--grey);
}
select:not(:disabled):hover,
button:not(:disabled):hover {
cursor: pointer;
background-color: var(--white-2);
}
legend {
display: flex;
gap: 0.5rem;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 2rem;
}
label {
display: flex;
gap: 0.5rem;
flex-direction: row;
align-items: center;
justify-content: flex-start;
height: 2rem;
}
fieldset {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 1px;
}
fieldset.vertical {
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
fieldset > * {
border-radius: 0;
}
fieldset > *:first-child {
border-radius: var(--input-border-radius) 0 0 var(--input-border-radius);
}
fieldset > *:last-child {
border-radius: 0 var(--input-border-radius) var(--input-border-radius) 0;
}
body {
--header-height: 4rem;
display: flex;
flex-direction: column;
min-height: 100lvh;
overflow: hidden;
perspective: 1000px;
}
#scene {
position: fixed;
transform-style: preserve-3d;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: calc(var(--header-height) + var(--padding)) var(--padding)
var(--padding);
inset: 0;
padding: var(--header-height) var(--padding);
}
#scene-content {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-style: preserve-3d;
backface-visibility: hidden;
}
#scene-content[data-layout="table"] {
display: grid;
grid-template-columns: repeat(18, var(--w));
grid-auto-rows: var(--h);
gap: 0.25rem;
place-content: center;
}
.controls {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
flex-shrink: 1;
position: fixed;
z-index: 3;
top: 0px;
width: 100%;
height: var(--header-height);
padding: var(--padding);
}
.controls-group {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
height: var(--header-height);
gap: calc(var(--padding) * 0.5);
}
.controls button {
background-color: var(--bg-4);
color: var(--fg-1);
flex-shrink: 1;
}
.controls button:hover {
background-color: var(--bg-5);
}
.controls button.is-active {
background-color: var(--fg-1);
color: var(--bg-3);
}
.credits {
text-align: center;
position: fixed;
z-index: 1;
bottom: 0px;
width: 100%;
height: var(--header-height);
padding: var(--padding);
font-size: 0.75rem;
}
.credits a {
color: var(--red-1);
text-decoration: none;
}
.element {
overflow: hidden;
pointer-events: auto;
--w: 3.5rem;
--h: 4rem;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
border: 1px solid var(--color-3);
background-color: var(--color-5);
color: var(--color-1);
width: var(--w);
height: var(--h);
border-radius: 0.25rem;
padding: 0.25rem;
transform-style: preserve-3d;
z-index: 8;
cursor: pointer;
}
button.element:focus-visible,
button.element:hover {
border: 1px solid var(--color-1);
background-color: var(--color-4);
color: var(--color-1);
outline: none;
}
.element.is-expanded {
--w: 13rem;
--h: 13rem;
z-index: 1;
padding: 1rem;
align-items: flex-start;
}
.element-description {
display: none;
transform-style: preserve-3d;
flex-direction: column;
gap: 0.25rem;
width: 100%;
font-size: 0.625rem;
text-transform: none;
color: var(--color-1);
justify-self: flex-end;
margin-top: 1.5rem;
text-align: left;
}
.element.is-expanded .element-description {
display: block;
}
.element-symbol {
transform-style: preserve-3d;
margin-top: -0.1rem;
margin-bottom: 0.2rem;
font-size: 1.5rem;
font-weight: 700;
}
.element.is-expanded .element-symbol {
margin-top: 0;
margin-bottom: 0;
font-size: 1rem;
text-align: left;
}
.element-title {
transform-style: preserve-3d;
font-size: 0.5rem;
font-weight: 700;
text-align: center;
}
.element.is-expanded .element-title {
font-size: 1.5rem;
text-align: left;
margin-top: 1rem;
}
.element-number {
transform-style: preserve-3d;
font-size: 0.5rem;
text-align: right;
align-self: flex-end;
}
.element.is-expanded .element-number {
position: absolute;
font-size: 1rem;
top: 1rem;
right: 1rem;
}
#scene-content[data-layout="sphere"] .element,
#scene-content[data-layout="helix"] .element,
#scene-content[data-layout="grid"] .element {
position: absolute;
left: 50%;
top: 50%;
margin-left: calc(var(--w) * -0.5);
margin-top: calc(var(--h) * -0.5);
}
#scene-content[data-layout="table"] .element.is-expanded {
left: -1.625rem;
top: -1.25rem;
margin-left: calc(var(--w) * -0.25);
margin-top: calc(var(--h) * -0.25);
margin-right: calc(var(--w) * -0.5);
margin-bottom: calc(var(--h) * -0.5);
}
</style>
</head>
<body>
<div class="controls">
<div class="controls-group display br">
<button class="is-active toggle" id="table">table</button>
<button class="toggle" id="sphere">sphere</button>
<button class="toggle" id="helix">helix</button>
<button class="toggle" id="grid">grid</button>
</div>
</div>
<div id="scene">
<div id="scene-content" data-layout="table"></div>
</div>
<template id="element">
<button class="element" data-color="0">
<div class="element-number"></div>
<div class="element-symbol"></div>
<div class="element-title"></div>
<div class="element-description"></div>
</button>
</template>
<script type="module">
import {
createLayout,
utils,
stagger,
spring,
createTimer,
createAnimatable,
} from "https://esm.sh/animejs@4.3.0";
// Elements data to populate the table
const ELEMENT_FIELDS = {
SYMBOL: 0,
NAME: 1,
COLOR: 2,
COLUMN: 3,
ROW: 4,
ATOMIC_MASS: 5,
DENSITY: 6,
MELTING_POINT: 7,
BOILING_POINT: 8,
};
const ELEMENT_STRIDE = 9;
const elements = [
// symbol, name, color, column, row, atomicMass, density, meltingPoint, boilingPoint
"H",
"Hydrogen",
0,
1,
1,
1.008,
0.08988,
-259.16,
-252.88,
"He",
"Helium",
1,
18,
1,
4.0026022,
0.1786,
-272.2,
-268.93,
"Li",
"Lithium",
2,
1,
2,
6.94,
0.534,
180.5,
1329.85,
"Be",
"Beryllium",
3,
2,
2,
9.01218315,
1.85,
1286.85,
2468.85,
"B",
"Boron",
4,
13,
2,
10.81,
2.08,
2075.85,
3926.85,
"C",
"Carbon",
0,
14,
2,
12.011,
1.821,
null,
null,
"N",
"Nitrogen",
0,
15,
2,
14.007,
1.251,
-210,
-195.79,
"O",
"Oxygen",
0,
16,
2,
15.999,
1.429,
-218.79,
-182.96,
"F",
"Fluorine",
0,
17,
2,
18.9984031636,
1.696,
-219.67,
-188.12,
"Ne",
"Neon",
1,
18,
2,
20.17976,
0.9002,
-248.59,
-246.05,
"Na",
"Sodium",
2,
1,
3,
22.989769282,
0.968,
97.79,
882.94,
"Mg",
"Magnesium",
3,
2,
3,
24.305,
1.738,
649.85,
1089.85,
"Al",
"Aluminium",
5,
13,
3,
26.98153857,
2.7,
660.32,
2469.85,
"Si",
"Silicon",
4,
14,
3,
28.085,
2.329,
1413.85,
3264.85,
"P",
"Phosphorus",
0,
15,
3,
30.9737619985,
1.823,
null,
null,
"S",
"Sulfur",
0,
16,
3,
32.06,
2.07,
115.21,
444.65,
"Cl",
"Chlorine",
0,
17,
3,
35.45,
3.2,
-101.55,
-34.04,
"Ar",
"Argon",
1,
18,
3,
39.9481,
1.784,
-189.34,
-185.85,
"K",
"Potassium",
2,
1,
4,
39.09831,
0.862,
63.55,
758.85,
"Ca",
"Calcium",
3,
2,
4,
40.0784,
1.55,
841.85,
1483.85,
"Sc",
"Scandium",
6,
3,
4,
44.9559085,
2.985,
1540.85,
2835.85,
"Ti",
"Titanium",
6,
4,
4,
47.8671,
4.506,
1667.85,
3286.85,
"V",
"Vanadium",
6,
5,
4,
50.94151,
6,
1909.85,
3406.85,
"Cr",
"Chromium",
6,
6,
4,
51.99616,
7.19,
1906.85,
2670.85,
"Mn",
"Manganese",
6,
7,
4,
54.9380443,
7.21,
1245.85,
2060.85,
"Fe",
"Iron",
6,
8,
4,
55.8452,
7.874,
1537.85,
2860.85,
"Co",
"Cobalt",
6,
9,
4,
58.9331944,
8.9,
1494.85,
2926.85,
"Ni",
"Nickel",
6,
10,
4,
58.69344,
8.908,
1454.85,
2729.85,
"Cu",
"Copper",
6,
11,
4,
63.5463,
8.96,
1084.62,
2561.85,
"Zn",
"Zinc",
5,
12,
4,
65.382,
7.14,
419.53,
906.85,
"Ga",
"Gallium",
5,
13,
4,
69.7231,
5.91,
29.76,
2399.85,
"Ge",
"Germanium",
4,
14,
4,
72.6308,
5.323,
938.25,
2832.85,
"As",
"Arsenic",
4,
15,
4,
74.9215956,
5.727,
null,
null,
"Se",
"Selenium",
0,
16,
4,
78.9718,
4.81,
220.85,
684.85,
"Br",
"Bromine",
0,
17,
4,
79.904,
3.1028,
-7.35,
58.85,
"Kr",
"Krypton",
1,
18,
4,
83.7982,
3.749,
-157.37,
-153.22,
"Rb",
"Rubidium",
2,
1,
5,
85.46783,
1.532,
39.3,
687.85,
"Sr",
"Strontium",
3,
2,
5,
87.621,
2.64,
776.85,
1376.85,
"Y",
"Yttrium",
6,
3,
5,
88.905842,
4.472,
1525.85,
2929.85,
"Zr",
"Zirconium",
6,
4,
5,
91.2242,
6.52,
1854.85,
4376.85,
"Nb",
"Niobium",
6,
5,
5,
92.906372,
8.57,
2476.85,
4743.85,
"Mo",
"Molybdenum",
6,
6,
5,
95.951,
10.28,
2622.85,
4638.85,
"Tc",
"Technetium",
6,
7,
5,
98,
11,
2156.85,
4264.85,
"Ru",
"Ruthenium",
6,
8,
5,
101.072,
12.45,
2333.85,
4149.85,
"Rh",
"Rhodium",
6,
9,
5,
102.905502,
12.41,
1963.85,
3694.85,
"Pd",
"Palladium",
6,
10,
5,
106.421,
12.023,
1554.9,
2962.85,
"Ag",
"Silver",
6,
11,
5,
107.86822,
10.49,
961.78,
2161.85,
"Cd",
"Cadmium",
5,
12,
5,
112.4144,
8.65,
321.07,
766.85,
"In",
"Indium",
5,
13,
5,
114.8181,
7.31,
156.6,
2071.85,
"Sn",
"Tin",
5,
14,
5,
118.7107,
7.365,
231.93,
2601.85,
"Sb",
"Antimony",
4,
15,
5,
121.7601,
6.697,
630.63,
1634.85,
"Te",
"Tellurium",
4,
16,
5,
127.603,
6.24,
449.51,
987.85,
"I",
"Iodine",
0,
17,
5,
126.904473,
4.933,
113.7,
184.25,
"Xe",
"Xenon",
1,
18,
5,
131.2936,
5.894,
-111.75,
-108.1,
"Cs",
"Cesium",
2,
1,
6,
132.905451966,
1.93,
28.55,
670.85,
"Ba",
"Barium",
3,
2,
6,
137.3277,
3.51,
726.85,
1844.85,
"Hf",
"Hafnium",
6,
4,
6,
178.492,
13.31,
2232.85,
4602.85,
"Ta",
"Tantalum",
6,
5,
6,
180.947882,
16.69,
3016.85,
5457.85,
"W",
"Tungsten",
6,
6,
6,
183.841,
19.25,
3421.85,
5929.85,
"Re",
"Rhenium",
6,
7,
6,
186.2071,
21.02,
3185.85,
5595.85,
"Os",
"Osmium",
6,
8,
6,
190.233,
22.59,
3032.85,
5011.85,
"Ir",
"Iridium",
6,
9,
6,
192.2173,
22.56,
2445.85,
4129.85,
"Pt",
"Platinum",
6,
10,
6,
195.0849,
21.45,
1768.25,
3824.85,
"Au",
"Gold",
6,
11,
6,
196.9665695,
19.3,
1064.18,
2969.85,
"Hg",
"Mercury",
5,
12,
6,
200.5923,
13.534,
-38.83,
356.73,
"Tl",
"Thallium",
5,
13,
6,
204.38,
11.85,
303.85,
1472.85,
"Pb",
"Lead",
5,
14,
6,
207.21,
11.34,
327.46,
1748.85,
"Bi",
"Bismuth",
5,
15,
6,
208.980401,
9.78,
271.55,
1563.85,
"Po",
"Polonium",
4,
16,
6,
209,
9.196,
253.85,
961.85,
"At",
"Astatine",
0,
17,
6,
210,
6.35,
301.85,
336.85,
"Rn",
"Radon",
1,
18,
6,
222,
9.73,
-71.15,
-61.65,
"Fr",
"Francium",
2,
1,
7,
223,
1.87,
26.85,
676.85,
"Ra",
"Radium",
3,
2,
7,
226,
5.5,
959.85,
1736.85,
"Rf",
"Rutherfordium",
6,
4,
7,
267,
23.2,
2126.85,
5526.85,
"Db",
"Dubnium",
6,
5,
7,
268,
29.3,
null,
null,
"Sg",
"Seaborgium",
6,
6,
7,
269,
35,
null,
null,
"Bh",
"Bohrium",
6,
7,
7,
270,
37.1,
null,
null,
"Hs",
"Hassium",
6,
8,
7,
269,
40.7,
-147.15,
null,
"Mt",
"Meitnerium",
9,
9,
7,
278,
37.4,
null,
null,
"Ds",
"Darmstadtium",
9,
10,
7,
281,
34.8,
null,
null,
"Rg",
"Roentgenium",
9,
11,
7,
282,
28.7,
null,
null,
"Cn",
"Copernicium",
9,
12,
7,
285,
14,
null,
3296.85,
"Nh",
"Nihonium",
9,
13,
7,
286,
16,
426.85,
1156.85,
"Fl",
"Flerovium",
9,
14,
7,
289,
14,
66.85,
146.85,
"Mc",
"Moscovium",
9,
15,
7,
289,
13.5,
396.85,
1126.85,
"Lv",
"Livermorium",
9,
16,
7,
293,
12.9,
435.85,
811.85,
"Ts",
"Tennessine",
9,
17,
7,
294,
7.17,
449.85,
609.85,
"Og",
"Oganesson",
9,
18,
7,
294,
4.95,
null,
76.85,
"La",
"Lanthanum",
7,
3,
8,
138.905477,
6.162,
919.85,
3463.85,
"Ce",
"Cerium",
7,
4,
8,
140.1161,
6.77,
794.85,
3442.85,
"Pr",
"Praseodymium",
7,
5,
8,
140.907662,
6.77,
934.85,
3129.85,
"Nd",
"Neodymium",
7,
6,
8,
144.2423,
7.01,
1023.85,
3073.85,
"Pm",
"Promethium",
7,
7,
8,
145,
7.26,
1041.85,
2999.85,
"Sm",
"Samarium",
7,
8,
8,
150.362,
7.52,
1071.85,
1899.85,
"Eu",
"Europium",
7,
9,
8,
151.9641,
5.264,
825.85,
1528.85,
"Gd",
"Gadolinium",
7,
10,
8,
157.253,
7.9,
1311.85,
2999.85,
"Tb",
"Terbium",
7,
11,
8,
158.925352,
8.23,
1355.85,
3122.85,
"Dy",
"Dysprosium",
7,
12,
8,
162.5001,
8.54,
1406.85,
2566.85,
"Ho",
"Holmium",
7,
13,
8,
164.930332,
8.79,
1460.85,
2599.85,
"Er",
"Erbium",
7,
14,
8,
167.2593,
9.066,
1528.85,
2867.85,
"Tm",
"Thulium",
7,
15,
8,
168.934222,
9.32,
1544.85,
1949.85,
"Yb",
"Ytterbium",
7,
16,
8,
173.0451,
6.9,
823.85,
1195.85,
"Lu",
"Lutetium",
7,
17,
8,
174.96681,
9.841,
1651.85,
3401.85,
"Ac",
"Actinium",
8,
3,
9,
227,
10,
1226.85,
3226.85,
"Th",
"Thorium",
8,
4,
9,
232.03774,
11.724,
1749.85,
4787.85,
"Pa",
"Protactinium",
8,
5,
9,
231.035882,
15.37,
1567.85,
4026.85,
"U",
"Uranium",
8,
6,
9,
238.028913,
19.1,
1132.15,
4130.85,
"Np",
"Neptunium",
8,
7,
9,
237,
20.45,
638.85,
4173.85,
"Pu",
"Plutonium",
8,
8,
9,
244,
19.816,
639.35,
3231.85,
"Am",
"Americium",
8,
9,
9,
243,
12,
1175.85,
2606.85,
"Cm",
"Curium",
8,
10,
9,
247,
13.51,
1339.85,
3109.85,
"Bk",
"Berkelium",
8,
11,
9,
247,
14.78,
985.85,
2626.85,
"Cf",
"Californium",
8,
12,
9,
251,
15.1,
899.85,
1469.85,
"Es",
"Einsteinium",
8,
13,
9,
252,
8.84,
859.85,
995.85,
"Fm",
"Fermium",
8,
14,
9,
257,
null,
1526.85,
null,
"Md",
"Mendelevium",
8,
15,
9,
258,
null,
826.85,
null,
"No",
"Nobelium",
8,
16,
9,
259,
null,
826.85,
null,
"Lr",
"Lawrencium",
8,
17,
9,
266,
null,
1626.85,
null,
];
// Genetate the table html
const [$sceneContent] = utils.$("#scene-content");
const [$template] = utils.$("#element");
for (let i = 0, l = elements.length / ELEMENT_STRIDE; i < l; i += 1) {
const offset = i * ELEMENT_STRIDE;
const $el = $template.content.cloneNode(true);
const $element = $el.querySelector(".element");
const $number = $element.querySelector(".element-number");
const $symbol = $element.querySelector(".element-symbol");
const $title = $element.querySelector(".element-title");
const $description = $element.querySelector(".element-description");
$number.textContent = i + 1;
$symbol.textContent = elements[offset + ELEMENT_FIELDS.SYMBOL];
$title.textContent = elements[offset + ELEMENT_FIELDS.NAME];
$element.dataset.color = elements[offset + ELEMENT_FIELDS.COLOR];
$element.style.gridColumn = elements[offset + ELEMENT_FIELDS.COLUMN];
$element.style.gridRow = elements[offset + ELEMENT_FIELDS.ROW];
$description.innerHTML = [
`Atomic mass: ${elements[offset + ELEMENT_FIELDS.ATOMIC_MASS]} u`,
`Density: ${elements[offset + ELEMENT_FIELDS.DENSITY]} g/cm3`,
`Melting: ${elements[offset + ELEMENT_FIELDS.MELTING_POINT]} °C`,
`Boiling: ${elements[offset + ELEMENT_FIELDS.BOILING_POINT]} °C`,
].join("<br>");
$sceneContent.appendChild($el);
}
const cards = utils.$("#scene-content .element");
// Create the cards layout and register the font-size as an extra property for animation
const elementsLayout = createLayout($sceneContent, {
properties: ["font-size"],
duration: 2000,
ease: "inOutExpo",
});
// Move the scene relative to the cursor position
const sceneAnimatable = createAnimatable("#scene", {
rotateX: 200,
rotateY: 200,
});
const pointer = { x: 0, y: 0, rotateX: 15, rotateY: 20, rx: 0, ry: 0 };
createTimer({
onUpdate: () => {
pointer.rx = utils.lerp(pointer.rx, pointer.rotateX, 0.01);
pointer.ry = utils.lerp(pointer.ry, pointer.rotateY, 0.01);
sceneAnimatable.rotateX(pointer.y * pointer.rx);
sceneAnimatable.rotateY(pointer.x * pointer.ry);
},
});
// The different layout tranform
const transformLayout = {
table: () => {
// The table view use CSS grid and has no special tranforms except for a selected element
pointer.rotateX = 15;
pointer.rotateY = 20;
cards.forEach(($el) => {
$el.style.opacity = 1;
$el.style.transform = $el.classList.contains("is-expanded")
? "translateZ(50px)"
: "translateZ(10px)";
});
},
sphere: () => {
const radius = 300;
pointer.rotateX = 40;
pointer.rotateY = 360;
cards.forEach(($el, i) => {
const offsetZ = $el.classList.contains("is-expanded") ? 20 : 0;
const phi = Math.acos(-1 + (2 * i) / cards.length);
const theta = Math.sqrt(cards.length * Math.PI) * phi;
const sinPhi = Math.sin(phi);
const x = radius * sinPhi * Math.cos(theta);
const y = radius * Math.cos(phi);
const z = radius * sinPhi * Math.sin(theta);
const yaw = Math.atan2(x, z);
const pitch = -Math.atan2(y, Math.hypot(x, z));
$el.style.transform = `translate3d(${x}px, ${y}px, ${z}px) rotateY(${yaw}rad) rotateX(${pitch}rad) translateZ(${offsetZ}px)`;
});
},
helix: () => {
pointer.rotateX = 30;
pointer.rotateY = 300;
const radius = 400;
const thetaStep = 0.16;
const verticalSpacing = 3;
const yOffset = (cards.length * verticalSpacing) / 2;
cards.forEach(($el, i) => {
const offsetZ = $el.classList.contains("is-expanded") ? 20 : 0;
const theta = i * thetaStep + Math.PI;
const y = -(i * verticalSpacing) + yOffset;
const x = radius * Math.sin(theta);
const z = radius * Math.cos(theta);
const yaw = Math.atan2(x, z);
const pitch = -Math.atan2(y, Math.hypot(x, z) * 2);
$el.style.transform = `translate3d(${x}px, ${y}px, ${z}px) rotateY(${yaw}rad) rotateX(${pitch}rad) translateZ(${offsetZ}px)`;
});
},
grid: () => {
pointer.rotateX = 10;
pointer.rotateY = 60;
const cols = 5;
const rows = 5;
const colGap = 150;
const rowGap = 100;
const depthGap = 150;
const perLayer = cols * rows;
const layers = Math.ceil(cards.length / perLayer);
cards.forEach(($el, i) => {
const offsetZ = $el.classList.contains("is-expanded") ? 20 : 0;
const col = i % cols;
const row = Math.floor(i / cols) % rows;
const layer = Math.floor(i / perLayer);
const x = (col - (cols - 1) / 2) * colGap;
const y = ((rows - 1) / 2 - row) * rowGap;
const z = offsetZ + (layer - (layers - 1) / 2) * depthGap;
$el.style.transform = `translate3d(${x}px, ${y}px, ${z}px)`;
});
},
random: () => {
// The table view use CSS grid and has no special tranforms except for a selected element
pointer.rotateX = 15;
pointer.rotateY = 20;
utils.set(cards, {
x: () => utils.random(-500, 500),
y: () => utils.random(-500, 500),
z: () => utils.random(-500, 500),
});
},
};
document.addEventListener("pointermove", (event) => {
const hw = window.innerWidth * 0.5;
const hh = window.innerHeight * 0.5;
pointer.x = utils.mapRange(event.clientX - hw, -hw, hw, 1, -1);
pointer.y = utils.mapRange(event.clientY - hh, -hh, hh, -1, 1);
});
const toggles = utils.$(".controls button.toggle");
document.addEventListener("click", (event) => {
const $toggle = event.target.closest(".controls button.toggle");
if ($toggle) {
toggles.forEach((button) => button.classList.remove("is-active"));
$toggle.classList.add("is-active");
const layoutType = $toggle.id;
elementsLayout.update(
() => {
cards.forEach(($el) => $el.classList.remove("is-expanded"));
$sceneContent.dataset.layout = layoutType;
transformLayout[layoutType]();
},
{
delay: stagger([0, 750], { from: "random" }),
},
);
return;
}
const $card = event.target.closest("#scene-content .element");
const shouldExpand = $card && !$card.classList.contains("is-expanded");
elementsLayout.update(
() => {
cards.forEach(($el) => $el.classList.remove("is-expanded"));
if (shouldExpand) $card.classList.add("is-expanded");
transformLayout[$sceneContent.dataset.layout]();
},
{
ease: spring({ bounce: 0.2, duration: 350 }),
},
);
});
document.addEventListener("keydown", (event) => {
if (event.key !== "Escape") return;
const hasExpandedCard = cards.some(($el) =>
$el.classList.contains("is-expanded"),
);
if (!hasExpandedCard) return;
elementsLayout.update(
() => {
cards.forEach(($el) => $el.classList.remove("is-expanded"));
transformLayout[$sceneContent.dataset.layout]();
},
{
ease: spring({ bounce: 0.3, duration: 350 }),
},
);
});
// Intro animation
transformLayout.random();
utils.set(cards, { opacity: 0 });
elementsLayout.update(() => transformLayout.table(), {
delay: stagger([0, 750], { from: "random" }),
});
</script>
</body>
</html>
