From Atoms to Web App: Crafting an Interactive Periodic Table Explorer 🧪💻
Learn Computer Academy

Learn Computer Academy @learncomputer

About: Website Design and Development Training Institute in Habra We provide the best hands-on training in Graphics Design, Website Design and Development.

Location:
Habra, India
Joined:
Mar 7, 2025

From Atoms to Web App: Crafting an Interactive Periodic Table Explorer 🧪💻

Publish Date: Apr 4
1 0

Introduction: When Code Meets Chemistry

Every developer has that moment when a project becomes more than just lines of code—it becomes a portal to learning. For me, that project was the Periodic Table Explorer, a web application that transforms the classic periodic table into an interactive, information-rich experience.

Check out the live project here - https://playground.learncomputer.in/periodic-table-explorer/

Why Another Periodic Table? 🤔

Let's be honest: most periodic tables are boring. They're static, lifeless grids that do little more than sit there. I wanted to create something different—a tool that would:

  • Spark curiosity about chemical elements
  • Make scientific information accessible
  • Provide an intuitive, engaging user experience

Project Overview: What We'll Build 🚀

An interactive web application that allows users to:

  • Browse elements in a visually appealing grid
  • Switch between grid and list views
  • Search elements quickly
  • Click on any element to reveal detailed information

Tech Stack 💻

  • Frontend: HTML5, CSS3
  • Interactivity: Vanilla JavaScript
  • Data Source: Public JSON API
  • Design: Responsive, modern UI

Setting Up the 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>
Enter fullscreen mode Exit fullscreen mode

Key HTML Components:

  • Main container
  • Search input
  • View toggle button
  • Modal for element details

Styling with CSS: Making It Look Awesome 🎨

* {
    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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Design Highlights:

  • Gradient background
  • Flexible grid layout
  • Responsive design
  • Smooth transitions and hover effects

JavaScript: The Interactive 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()] || '';
    }
});
Enter fullscreen mode Exit fullscreen mode

Core Functions:

  • Fetching element data
  • Creating periodic table grid
  • Handling element clicks
  • Implementing search functionality

Data Fetching: Bringing Elements to Life 📊

 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));
Enter fullscreen mode Exit fullscreen mode

Creating the Periodic Table Grid 🏗️

function createPeriodicTable(elements) {
    // Dynamically generate element tiles
    // Position elements in correct grid locations
}
Enter fullscreen mode Exit fullscreen mode

The Element Modal: A Deep Dive 🔍

When users click an element, they'll see:

  • Atomic details
  • Physical properties
  • Historical information
  • Spectral images (where available)
function showElementDetails(element) {
    // Populate modal with comprehensive element information
}
Enter fullscreen mode Exit fullscreen mode

Innovative Features 🌟

  1. Dynamic View Modes

    • Grid view (classic periodic table)
    • List view (detailed, scrollable)
  2. Real-time Search

    • Instantly filter elements
    • Search by name or symbol
  3. Responsive Design

    • Works perfectly on desktop and mobile

Challenges and Learnings 🧠

Building this app wasn't just about coding—it was about:

  • Managing complex data structures
  • Creating intuitive user interfaces
  • Optimizing performance
  • Making science accessible

Performance Considerations 🚄

  • Efficient data loading
  • Minimal DOM manipulations
  • Lightweight, fast-loading design

Future Roadmap 🗺️

Potential enhancements:

  • 3D element visualizations
  • Interactive learning quizzes
  • More detailed scientific comparisons

Live Demo 🔗

Closing Thoughts: More Than Just a Table 🌈

The Periodic Table Explorer is a testament to how web development can transform educational content. It's not just about displaying information—it's about creating an experience that inspires curiosity and makes learning fun.

Learning Opportunities 📚

This project is perfect for developers who want to:

  • Practice vanilla JavaScript
  • Work with JSON data
  • Create interactive web applications
  • Understand responsive design principles

Happy Coding and Exploring! 🚀👩‍💻👨‍💻

Crafted with curiosity, code, and a dash of chemical enthusiasm


🔍 Quick Tips for Aspiring Developers

  • Always think about user experience
  • Make your projects interactive
  • Never stop learning!

Comments 0 total

    Add comment