Unveiling the Periodic Table Explorer: A Modern Web Development Marvel π§ͺβ¨

Hey there, fellow tech enthusiasts and curious learners! Today, I'm excited to walk you through an incredible web development project that brings the fascinating world of chemistry right to your browser: the Periodic Table Explorer. ππ¬
Check out the live project here - https://playground.learncomputer.in/periodic-table-explorer/
The Inspiration Behind the Project π‘
As someone who's always been fascinated by the intricate world of elements, I wanted to create something that wasn't just another static periodic table. The goal? An interactive, visually stunning, and information-rich experience that makes learning about chemical elements both fun and accessible.
Key Features at a Glance π
Before we dive into the technical details, let me highlight what makes this Periodic Table Explorer special:
Interactive Element Tiles: Each element is a clickable gateway to a world of information
Dynamic View Modes: Switch between grid and list views effortlessly
Real-time Search: Find elements instantly with a smart search feature
Detailed Element Modal: Click an element to reveal its complete scientific profile
Responsive Design: Works beautifully on desktops, tablets, and mobile devices
The Technology Stack π»
HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Periodic Table Explorer</title>
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&family=Roboto:wght@300;400;700&family=Lato:wght@300;400;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<header>
<h1>Periodic Table Explorer</h1>
<div class="controls">
<input type="text" id="search" placeholder="Search elements...">
<button id="view-toggle">List View</button>
</div>
</header>
<div class="periodic-table" id="periodic-table"></div>
<div class="modal" id="element-modal">
<div class="modal-content">
<span class="close">Γ</span>
<div class="modal-body" id="modal-body">
<div class="info-section" id="info-section"></div>
<div id="three-container" style="width: 100%; height: 400px;"></div>
</div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Styling with CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #1e1e1e, #2c3e50);
color: #fff;
min-height: 100vh;
font-family: 'Roboto', sans-serif;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 40px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
background: rgba(255, 255, 255, 0.05);
padding: 20px;
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
h1 {
font-size: 2.8rem;
font-weight: 700;
color: #00ffcc;
font-family: 'Lato', sans-serif;
}
.controls {
display: flex;
gap: 15px;
}
input, button {
padding: 12px 25px;
border: none;
border-radius: 50px;
font-size: 1rem;
transition: all 0.4s ease;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
font-family: 'Poppins', sans-serif;
}
input {
backdrop-filter: blur(5px);
color: #fff;
}
input::placeholder {
color: rgba(255, 255, 255, 0.7);
opacity: 1;
}
input:focus {
outline: none;
box-shadow: 0 0 15px rgba(0, 255, 204, 0.5);
}
button {
background: #00ffcc;
color: #1e1e1e;
cursor: pointer;
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0, 255, 204, 0.4);
}
.periodic-table {
display: grid;
grid-template-columns: repeat(18, 1fr);
grid-template-rows: repeat(10, 1fr);
gap: 6px;
padding: 20px;
background: rgba(255, 255, 255, 0.03);
border-radius: 25px;
backdrop-filter: blur(15px);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
min-height: 500px;
overflow: auto;
}
.periodic-table.list-view {
display: flex;
flex-direction: column;
gap: 8px;
}
.element {
width: 60px;
height: 60px;
display: grid;
grid-template-areas:
"number ."
"symbol symbol"
"name name";
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr auto;
justify-items: center;
align-items: center;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 4px;
}
.periodic-table.list-view .element {
width: 100%;
height: auto;
padding: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.element:hover {
transform: scale(1.1) rotate(2deg);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
filter: brightness(1.2);
}
.periodic-table.list-view .element:hover {
transform: scale(1.05);
rotate: 0deg;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.element .number {
grid-area: number;
font-size: 0.7rem;
}
.element .symbol {
grid-area: symbol;
font-size: 1.1rem;
font-weight: 600;
}
.element .name {
grid-area: name;
font-size: 0.6rem;
opacity: 0;
transition: opacity 0.3s ease;
}
.element:hover .name {
opacity: 1;
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(3px);
z-index: 1000;
}
.modal-content {
background: rgba(30, 30, 30, 0.95);
margin: 5% auto;
padding: 30px;
width: 90%;
max-width: 800px;
border-radius: 20px;
position: relative;
animation: slideIn 0.4s ease;
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5);
color: #fff;
max-height: 80vh;
overflow-y: auto;
}
@keyframes slideIn {
from { transform: translateY(-100px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.close {
position: absolute;
right: 20px;
top: 15px;
font-size: 30px;
cursor: pointer;
color: #fff;
transition: all 0.3s ease;
}
.close:hover {
color: #ff6666;
transform: rotate(90deg);
}
.modal-body h2 {
color: #00ffcc;
margin-bottom: 20px;
font-size: 2rem;
font-family: 'Lato', sans-serif;
}
.modal-body .info-grid {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 15px;
font-family: 'Poppins', sans-serif;
}
.modal-body .info-grid p {
margin: 5px 0;
}
.modal-body .info-grid p strong {
color: #00ffcc;
font-weight: 700;
}
.modal-body .info-grid p a {
color: #66ccff;
text-decoration: underline;
}
.modal-body .info-grid p a:hover {
color: #99eeff;
}
.modal-body .info-grid p img {
max-width: 100%;
height: auto;
margin-top: 10px;
border-radius: 10px;
}
#three-container {
width: 100%;
height: 400px;
margin-top: 20px;
border-radius: 10px;
overflow: hidden;
background: #222;
}
/* High-contrast colors */
.alkali-metal { background: #ff6b6b; color: #000; }
.alkaline-earth-metal { background: #ffa94d; color: #000; }
.transition-metal { background: #ffd700; color: #000; }
.post-transition-metal { background: #95e063; color: #000; }
.metalloid { background: #63e6be; color: #000; }
.nonmetal { background: #63cdda; color: #000; }
.noble-gas { background: #74c0fc; color: #000; }
.lanthanide { background: #cc99ff; color: #000; }
.actinide { background: #ff99cc; color: #000; }
/* Ensure text inherits color from parent */
.element .number,
.element .symbol,
.element .name {
color: inherit;
}
/* Responsive Design */
@media (max-width: 768px) {
.periodic-table {
grid-template-columns: repeat(9, 1fr);
}
header {
flex-direction: column;
gap: 20px;
}
.modal-body .info-grid {
grid-template-columns: 1fr;
}
.element {
width: 50px;
height: 50px;
}
}
JavaScript Magic π
document.addEventListener('DOMContentLoaded', () => {
const table = document.getElementById('periodic-table');
const modal = document.getElementById('element-modal');
const modalBody = document.getElementById('modal-body');
const closeBtn = document.querySelector('.close');
const viewToggle = document.getElementById('view-toggle');
const searchInput = document.getElementById('search');
fetch('PeriodicTableJSON.json')
.then(response => response.json())
.then(data => {
const elements = data.elements;
createPeriodicTable(elements);
setupEventListeners(elements);
})
.catch(error => console.error('Error fetching data:', error));
function createPeriodicTable(elements) {
table.innerHTML = '';
elements.forEach(element => {
const div = document.createElement('div');
div.className = `element ${getCategoryClass(element.category)}`;
div.innerHTML = `
<span class="number">${element.number}</span>
<span class="symbol">${element.symbol}</span>
<span class="name">${element.name}</span>
`;
div.dataset.atomicNumber = element.number;
const period = element.ypos;
const group = element.xpos;
let row = period;
let col = group;
if (element.number >= 58 && element.number <= 71) {
row = 8;
col = element.number - 55;
} else if (element.number >= 90 && element.number <= 103) {
row = 9;
col = element.number - 87;
}
div.style.gridRow = row;
div.style.gridColumn = col;
table.appendChild(div);
});
}
function setupEventListeners(elements) {
const elementNodes = document.querySelectorAll('.element');
elementNodes.forEach(element => {
element.addEventListener('click', (e) => {
e.stopPropagation();
const atomicNumber = parseInt(element.dataset.atomicNumber, 10);
const elementData = elements.find(el => el.number === atomicNumber);
if (elementData) {
showModal(elementData);
}
});
});
closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
modal.style.display = 'none';
});
document.addEventListener('click', (e) => {
if (e.target === modal) {
modal.style.display = 'none';
}
});
viewToggle.addEventListener('click', () => {
table.classList.toggle('list-view');
viewToggle.textContent = table.classList.contains('list-view') ? 'Grid View' : 'List View';
if (table.classList.contains('list-view')) {
document.querySelectorAll('.element').forEach(el => {
el.style.gridRow = 'auto';
el.style.gridColumn = 'auto';
});
} else {
createPeriodicTable(elements);
}
});
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
document.querySelectorAll('.element').forEach(el => {
const name = el.querySelector('.name').textContent.toLowerCase();
const symbol = el.querySelector('.symbol').textContent.toLowerCase();
el.style.display = (name.includes(searchTerm) || symbol.includes(searchTerm)) ? '' : 'none';
});
});
}
function showModal(element) {
modalBody.innerHTML = `
<h2>${element.name} (${element.symbol})</h2>
<div class="info-grid">
<p><strong>Atomic Number:</strong> ${element.number}</p>
<p><strong>Atomic Mass:</strong> ${element.atomic_mass}</p>
<p><strong>Symbol:</strong> ${element.symbol}</p>
<p><strong>Category:</strong> ${element.category}</p>
<p><strong>Period:</strong> ${element.ypos}</p>
<p><strong>Group:</strong> ${element.xpos}</p>
<p><strong>Density:</strong> ${element.density || 'N/A'} g/cmΒ³</p>
<p><strong>Melting Point:</strong> ${element.melt || 'N/A'} K</p>
<p><strong>Boiling Point:</strong> ${element.boil || 'N/A'} K</p>
<p><strong>Appearance:</strong> ${element.appearance || 'N/A'}</p>
<p><strong>Phase:</strong> ${element.phase || 'N/A'}</p>
<p><strong>Molar Heat:</strong> ${element.molar_heat || 'N/A'} J/(molΒ·K)</p>
<p><strong>Discovered By:</strong> ${element.discovered_by || 'N/A'}</p>
<p><strong>Named By:</strong> ${element.named_by || 'N/A'}</p>
<p><strong>Summary:</strong> ${element.summary || 'N/A'}</p>
<p><strong>Electron Configuration:</strong> ${element.electron_configuration || 'N/A'}</p>
<p><strong>Semantic Electron Config:</strong> ${element.electron_configuration_semantic || 'N/A'}</p>
<p><strong>Shells:</strong> ${element.shells ? element.shells.join(', ') : 'N/A'}</p>
<p><strong>Ionization Energies:</strong> ${element.ionization_energies ? element.ionization_energies.join(', ') : 'N/A'} kJ/mol</p>
${element.spectral_img ? `<p><strong>Spectral Image:</strong> <a href="${element.spectral_img}" target="_blank">View Spectral Image</a></p>` : '<p><strong>Spectral Image:</strong> N/A</p>'}
${element.bohr_model_image ? `<p><strong>Bohr Model Image:</strong> <img src="${element.bohr_model_image}" alt="Bohr Model of ${element.name}" style="max-width: 200px;"></p>` : '<p><strong>Bohr Model Image:</strong> N/A</p>'}
${element.source ? `<p><strong>Source:</strong> <a href="${element.source}" target="_blank">Source</a></p>` : '<p><strong>Source:</strong> N/A</p>'}
${element.image ? `
<p><strong>Image Title:</strong> ${element.image.title || 'N/A'}</p>
<p><strong>Image:</strong> <img src="${element.image.url}" alt="${element.image.title || 'Element Image'}" style="max-width: 200px;"></p>
` : '<p><strong>Image:</strong> N/A</p>'}
</div>
`;
modal.style.display = 'block';
}
function getCategoryClass(category) {
const classes = {
'alkali metal': 'alkali-metal',
'alkaline earth metal': 'alkaline-earth-metal',
'transition metal': 'transition-metal',
'post-transition metal': 'post-transition-metal',
'metalloid': 'metalloid',
'nonmetal': 'nonmetal',
'noble gas': 'noble-gas',
'lanthanide': 'lanthanide',
'actinide': 'actinide'
};
return classes[category.toLowerCase()] || '';
}
});
Fetching Element Data π
One of the coolest aspects of this project is how we retrieve element information. We're using a publicly available JSON dataset from GitHub, which provides comprehensive details about every element in the periodic table.
fetch('PeriodicTableJSON.json')
.then(response => response.json())
.then(data => {
const elements = data.elements;
createPeriodicTable(elements);
setupEventListeners(elements);
})
.catch(error => console.error('Error fetching data:', error));
Creating the Periodic Table Grid ποΈ
The magic happens in the createPeriodicTable() function. Here's how we dynamically generate element tiles:
function createPeriodicTable(elements) {
table.innerHTML = '';
elements.forEach(element => {
const div = document.createElement('div');
div.className = `element ${getCategoryClass(element.category)}`;
div.innerHTML = `
<span class="number">${element.number}</span>
<span class="symbol">${element.symbol}</span>
<span class="name">${element.name}</span>
`;
div.dataset.atomicNumber = element.number;
const period = element.ypos;
const group = element.xpos;
let row = period;
let col = group;
if (element.number >= 58 && element.number <= 71) {
row = 8;
col = element.number - 55;
} else if (element.number >= 90 && element.number <= 103) {
row = 9;
col = element.number - 87;
}
div.style.gridRow = row;
div.style.gridColumn = col;
table.appendChild(div);
});
}
Interactive Modal: A Deep Dive into Elements π
When you click an element, a beautiful modal appears with a treasure trove of information:
Atomic number and mass
Electron configuration
Melting and boiling points
Discovery details
And so much more!
Search and View Toggling: Enhancing User Experience πΉοΈ
We've added two killer features to make navigation a breeze:
Smart Search: Instantly filter elements by name or symbol
View Toggle: Switch between compact grid and detailed list views
Design Considerations π¨
Accessibility and Responsiveness
Mobile-friendly layout
High-contrast color scheme
Smooth animations and transitions
Performance Optimization
Efficient data fetching
Minimal DOM manipulation
Lightweight and fast-loading
Challenges and Learning Opportunities π§
Building this project wasn't without its challenges. Some key hurdles included:
Managing complex grid layouts
Handling dynamic data rendering
Creating a responsive design that looks great everywhere
Next Steps and Future Improvements π
While the current version is awesome, here are some potential enhancements:
Add more interactive 3D element visualizations
Implement advanced filtering options
Create educational quizzes based on element data
Conclusion: Learning Through Interaction π
The Periodic Table Explorer is more than just a web appβit's an interactive learning experience that makes science accessible and exciting. Whether you're a student, teacher, or just curious about the building blocks of our universe, this tool has something for everyone.
Ready to Explore? π¬
Check out the live demo: Periodic Table Explorer
Happy exploring, and may your curiosity about the elements never fade! π
Pro Tip: Open the developer console and hover over elementsβyou might discover some hidden easter eggs! π
Built with β€οΈ by a passionate web developer and chemistry enthusiast






