Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> JavaScript

วิธีสร้าง Vertical Tab Menu (แท็บแนวตั้ง) ด้วย CSS และ JavaScript

สร้าง Vertical Tabs - แท็บจัดเรียงแนวตั้งด้านซ้าย เนื้อหาแสดงด้านขวา เหมาะสำหรับ Sidebar Navigation, Settings Panel, Documentation

โค้ดตัวอย่างสมบูรณ์

<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vertical Tab Menu</title>
  <style>
    * { box-sizing: border-box; }
    
    body {
      font-family: 'Segoe UI', Tahoma, sans-serif;
      margin: 0;
      padding: 24px;
      background: #f5f5f5;
    }
    
    .vertical-tabs {
      display: flex;
      max-width: 900px;
      margin: 0 auto;
      background: #fff;
      border-radius: 12px;
      box-shadow: 0 4px 20px rgba(0,0,0,0.08);
      overflow: hidden;
    }
    
    /* Tab Sidebar - แนวตั้ง */
    .tab-sidebar {
      width: 220px;
      min-width: 220px;
      background: #1a1a2e; /* สีเข้ม */
      border-right: 1px solid #2d2d44;
    }
    
    .tab-btn {
      display: block;
      width: 100%;
      padding: 18px 24px;
      background: transparent;
      border: none;
      border-left: 3px solid transparent;
      text-align: left;
      cursor: pointer;
      font-size: 15px;
      font-weight: 500;
      color: #a0a0b0;
      transition: all 0.25s ease;
      font-family: inherit;
    }
    
    .tab-btn:hover {
      background: rgba(255,255,255,0.05);
      color: #fff;
    }
    
    .tab-btn.active {
      background: rgba(0, 100, 0, 0.15); /* สีเขียวอ่อน */
      border-left-color: #006400;
      color: #00c851;
      font-weight: 600;
    }
    
    .tab-btn:focus {
      outline: none;
      box-shadow: inset 3px 0 0 #006400;
    }
    
    /* Content Area */
    .tab-content-area {
      flex: 1;
      padding: 40px;
      min-height: 350px;
    }
    
    .tab-panel {
      display: none;
      animation: slideIn 0.3s ease;
    }
    
    .tab-panel.active {
      display: block;
    }
    
    @keyframes slideIn {
      from { opacity: 0; transform: translateX(-20px); }
      to { opacity: 1; transform: translateX(0); }
    }
    
    .tab-panel h3 {
      margin: 0 0 16px;
      color: #1a1a2e;
      font-size: 24px;
    }
    
    .tab-panel p {
      color: #555;
      line-height: 1.8;
      font-size: 16px;
    }
    
    /* Responsive: Mobile Stack */
    @media (max-width: 768px) {
      .vertical-tabs {
        flex-direction: column;
      }
      
      .tab-sidebar {
        width: 100%;
        border-right: none;
        border-bottom: 1px solid #2d2d44;
      }
      
      .tab-btn {
        border-left: none;
        border-bottom: 3px solid transparent;
        padding: 14px 20px;
      }
      
      .tab-btn.active {
        border-left-color: transparent;
        border-bottom-color: #006400;
        background: rgba(0, 100, 0, 0.1);
      }
      
      .tab-content-area {
        padding: 24px;
        min-height: auto;
      }
    }
  </style>
</head>
<body>
  <h2>ข้อมูลการสอบ</h2>
  <p>ข้อมูลเกี่ยวกับการสอบครั้งนี้:</p>
  
  <div class="vertical-tabs">
    <div class="tab-sidebar" role="tablist" aria-label="Exam Information">
      <button class="tab-btn active" role="tab" aria-selected="true" aria-controls="panel-dates" onclick="showInfo('dates', this)">
        📅 วันสอบ
      </button>
      <button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-centre" onclick="showInfo('centre', this)">
        📍 สถานที่สอบ
      </button>
    </div>
    
    <div class="tab-content-area">
      <div id="panel-dates" class="tab-panel active" role="tabpanel">
        <h3>📅 วันสอบ</h3>
        <p>วันสอบจะถูกประกาศอย่างเป็นทางการวันที่ 25 มีนาคม 2567 โปรดติดตามข่าวสารเพิ่มเติม</p>
        <p>การสอบจะดำเนินการต่อเนื่องเป็นเวลา 3 วัน ตั้งแต่วันที่ประกาศ</p>
      </div>
      
      <div id="panel-centre" class="tab-panel" role="tabpanel">
        <h3>📍 สถานที่สอบ</h3>
        <p>สถานที่สอบจะถูกประกาศวันที่ 30 มีนาคม 2567 ผู้สมัครสามารถตรวจสอบได้ที่เว็บไซต์ทางการ</p>
        <p>จะมีสถานที่สอบกระจายอยู่หลายจังหวัด ให้เลือกสถานที่ที่สะดวกที่สุด</p>
      </div>
    </div>
  </div>
  
  <script>
    function showInfo(infoId, btnElement) {
      // ซ่อนทุก Panel
      document.querySelectorAll('.tab-panel').forEach(panel => {
        panel.classList.remove('active');
      });
      
      // Reset ทุกปุ่ม
      document.querySelectorAll('.tab-btn').forEach(btn => {
        btn.classList.remove('active');
        btn.setAttribute('aria-selected', 'false');
      });
      
      // แสดง Panel ที่เลือก
      const targetPanel = document.getElementById('panel-' + infoId);
      if (targetPanel) {
        targetPanel.classList.add('active');
      }
      
      // Active ปุ่มที่คลิก
      btnElement.classList.add('active');
      btnElement.setAttribute('aria-selected', 'true');
    }
    
    // Auto-activate first tab
    document.addEventListener('DOMContentLoaded', () => {
      const firstBtn = document.querySelector('.tab-btn.active');
      if (firstBtn) firstBtn.click();
    });
  </script>
</body>
</html>

หลักการทำงาน

  1. Layout: Flex Container → Sidebar (Fixed Width) + Content Area (Flex: 1)
  2. Tab Buttons: display: block; width: 100% จัดแนวตั้ง
  3. Active Indicator: border-left (Desktop) / border-bottom (Mobile)
  4. JS Logic: เหมือน Horizontal Tabs - Toggle Class active

การปรับปรุงสำหรับ Production

1. Keyboard Navigation (Arrow Up/Down)

document.querySelectorAll('.tab-btn').forEach((btn, index, tabs) => {
  btn.addEventListener('keydown', (e) => {
    let nextIndex = index;
    if (e.key === 'ArrowDown') nextIndex = (index + 1) % tabs.length;
    else if (e.key === 'ArrowUp') nextIndex = (index - 1 + tabs.length) % tabs.length;
    else if (e.key === 'Home') nextIndex = 0;
    else if (e.key === 'End') nextIndex = tabs.length - 1;
    else return;
    
    e.preventDefault();
    tabs[nextIndex].focus();
    tabs[nextIndex].click();
  });
});

2. Nested Tabs (Sub-tabs)

/* CSS สำหรับ Sub-tabs */
.tab-btn.has-submenu { position: relative; }
.tab-btn.has-submenu::after { 
  content: '▶'; 
  float: right; 
  font-size: 10px; 
  transition: transform 0.2s;
}
.tab-btn.has-submenu.open::after { transform: rotate(90deg); }

.submenu {
  display: none;
  padding-left: 16px;
  border-left: 2px solid #333;
  background: rgba(0,0,0,0.1);
}
.submenu.open { display: block; }

3. Persist Active Tab (URL Hash / localStorage)

// Save to URL Hash
function showInfo(infoId, btnElement) {
  // ... switching logic ...
  history.replaceState(null, '', '#' + infoId); // Update URL without reload
}

// Restore from Hash on Load
document.addEventListener('DOMContentLoaded', () => {
  const hash = location.hash.slice(1); // Remove #
  if (hash) {
    const btn = document.querySelector(`[aria-controls="panel-${hash}"]`);
    if (btn) btn.click();
  }
});

กรณีใช้งานที่เหมาะสม

  • Settings / Preferences Panels
  • Documentation Sidebar
  • Admin Dashboard Navigation
  • Multi-step Forms / Wizards
  • Product Feature Comparison
  • Help / FAQ Categories