Skip to main content

Command Palette

Search for a command to run...

Unveiling the Periodic Table Explorer: A Modern Web Development Marvel πŸ§ͺ✨

Published
β€’9 min readβ€’View as Markdown
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:

  1. Smart Search: Instantly filter elements by name or symbol

  2. 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

More from this blog

L

Learn Computer Academy

50 posts

Website Design and Development Training Center in Habra. We offer top-notch practical training in Graphics Design, Website Design, and Development.