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

วิธีสร้าง Tab Headers (แท็บหัวข้อ) ด้วย CSS และ JavaScript

สร้าง Tab Headers แบบคลาสสิก - แท็บอยู่ด้านบน เนื้อหาแสดงด้านล่าง เปลี่ยนแท็บโดยการ คลิก (Click)

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

<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Tab Headers Example</title>
  <style>
    * { box-sizing: border-box; }
    
    body {
      font-family: 'Segoe UI', Tahoma, sans-serif;
      padding: 24px;
      max-width: 800px;
      margin: 0 auto;
      background: #f5f5f5;
    }
    
    /* Tab Buttons Container */
    .tab-headers {
      display: flex;
      border-bottom: 2px solid #ddd;
      margin-bottom: 0;
      background: #fff;
      border-radius: 8px 8px 0 0;
      overflow: hidden;
      box-shadow: 0 2px 8px rgba(0,0,0,0.08);
    }
    
    .tab-btn {
      flex: 1;
      padding: 16px 24px;
      background: transparent;
      border: none;
      border-bottom: 3px solid transparent;
      cursor: pointer;
      font-size: 16px;
      font-weight: 500;
      color: #666;
      transition: all 0.2s ease;
      position: relative;
    }
    
    .tab-btn:hover {
      color: #333;
      background: #f8f9fa;
    }
    
    .tab-btn.active {
      color: #0066cc;
      border-bottom-color: #0066cc;
      background: #fff;
    }
    
    .tab-btn.active::after {
      content: '';
      position: absolute;
      bottom: -2px;
      left: 0;
      right: 0;
      height: 3px;
      background: #0066cc;
    }
    
    /* Tab Content Panels */
    .tab-content {
      background: #fff;
      border-radius: 0 0 8px 8px;
      padding: 32px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.08);
      min-height: 200px;
    }
    
    .tab-panel {
      display: none;
      animation: fadeIn 0.25s ease;
    }
    
    .tab-panel.active {
      display: block;
    }
    
    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(8px); }
      to { opacity: 1; transform: translateY(0); }
    }
    
    .tab-panel h3 {
      margin-top: 0;
      margin-bottom: 16px;
      color: #333;
    }
    
    .tab-panel p {
      color: #555;
      line-height: 1.7;
    }
  </style>
</head>
<body>
  <h2>ตารางการแข่งขัน</h2>
  
  <!-- Tab Headers -->
  <div class="tab-headers" role="tablist">
    <button class="tab-btn active" role="tab" aria-selected="true" aria-controls="panel-date" onclick="switchTab('date', this, '#e74c3c')">
      📅 วันที่
    </button>
    <button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-time" onclick="switchTab('time', this, '#f39c12')">
      🕐 เวลา
    </button>
    <button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-venue" onclick="switchTab('venue', this, '#27ae60')">
      📍 สถานที่
    </button>
  </div>
  
  <!-- Tab Panels -->
  <div class="tab-content">
    <div id="panel-date" class="tab-panel active" role="tabpanel">
      <h3>📅 วันที่การแข่งขัน</h3>
      <p>การแข่งขันจะจัดขึ้นวันที่ 2 เมษายน 2567 (วันอังคาร)</p>
      <p>เปิดรับการลงทะเบียนตั้งแต่วันที่ 1 มีนาคม 2567 เป็นต้นไป</p>
    </div>
    
    <div id="panel-time" class="tab-panel" role="tabpanel">
      <h3>🕐 เวลาการแข่งขัน</h3>
      <p>เริ่มต้นเวลา 09:00 น. จนจบประมาณ 18:00 น.</p>
      <p>การลงทะเบียนรายวันเริ่มเวลา 08:00 น. ก่อนเปิดประตูสนาม</p>
    </div>
    
    <div id="panel-venue" class="tab-panel" role="tabpanel">
      <h3>📍 สถานที่จัดการแข่งขัน</h3>
      <p>สนามกีฬาเมลเบิร์น คริเก็ต 그라운드 (MCG) เมลเบิร์น, ออสเตรเลีย</p>
      <p>ที่นั่ง 100,000 ที่นั่ง ใกล้สถานีรถไฟ Richmond Station</p>
    </div>
  </div>
  
  <script>
    function switchTab(panelId, btnElement, activeColor) {
      // 1. ซ่อนทุก Panel
      document.querySelectorAll('.tab-panel').forEach(panel => {
        panel.classList.remove('active');
      });
      
      // 2. Reset ทุกปุ่ม
      document.querySelectorAll('.tab-btn').forEach(btn => {
        btn.classList.remove('active');
        btn.setAttribute('aria-selected', 'false');
        btn.style.borderBottomColor = 'transparent';
      });
      
      // 3. แสดง Panel ที่เลือก
      const targetPanel = document.getElementById('panel-' + panelId);
      if (targetPanel) {
        targetPanel.classList.add('active');
      }
      
      // 4. Active ปุ่มที่คลิก
      btnElement.classList.add('active');
      btnElement.setAttribute('aria-selected', 'true');
      btnElement.style.borderBottomColor = activeColor;
    }
    
    // Auto-activate first tab on load
    document.addEventListener('DOMContentLoaded', () => {
      const firstBtn = document.querySelector('.tab-btn.active');
      if (firstBtn) {
        firstBtn.click(); // Trigger click เพื่อให้ Active state ถูกต้อง
      }
    });
  </script>
</body>
</html>

โครงสร้าง HTML แบบ Semantic (ARIA Pattern)

<div role="tablist" aria-label="Match Schedule">
  <button role="tab" aria-selected="true" aria-controls="panel-date" id="tab-date">Date</button>
  <button role="tab" aria-selected="false" aria-controls="panel-time" id="tab-time">Time</button>
  <button role="tab" aria-selected="false" aria-controls="panel-venue" id="tab-venue">Venue</button>
</div>

<div role="tabpanel" id="panel-date" aria-labelledby="tab-date">...</div>
<div role="tabpanel" id="panel-time" aria-labelledby="tab-time" hidden>...</div>
<div role="tabpanel" id="panel-venue" aria-labelledby="tab-venue" hidden>...</div>

JavaScript Module Pattern (Reusable)

class TabManager {
  constructor(containerSelector) {
    this.container = document.querySelector(containerSelector);
    this.tabs = this.container.querySelectorAll('[role="tab"]');
    this.panels = this.container.querySelectorAll('[role="tabpanel"]');
    this.init();
  }
  
  init() {
    this.tabs.forEach(tab => {
      tab.addEventListener('click', () => this.activateTab(tab));
      tab.addEventListener('keydown', (e) => this.handleKeydown(e));
    });
    
    // Activate first tab
    this.activateTab(this.tabs[0]);
  }
  
  activateTab(selectedTab) {
    const panelId = selectedTab.getAttribute('aria-controls');
    
    this.tabs.forEach(tab => {
      tab.setAttribute('aria-selected', 'false');
      tab.classList.remove('active');
    });
    
    this.panels.forEach(panel => {
      panel.hidden = true;
      panel.classList.remove('active');
    });
    
    selectedTab.setAttribute('aria-selected', 'true');
    selectedTab.classList.add('active');
    
    const targetPanel = document.getElementById(panelId);
    if (targetPanel) {
      targetPanel.hidden = false;
      targetPanel.classList.add('active');
    }
  }
  
  handleKeydown(e) {
    const tabs = Array.from(this.tabs);
    const currentIndex = tabs.indexOf(e.target);
    let nextIndex = currentIndex;
    
    switch(e.key) {
      case 'ArrowRight': nextIndex = (currentIndex + 1) % tabs.length; break;
      case 'ArrowLeft': nextIndex = (currentIndex - 1 + tabs.length) % tabs.length; break;
      case 'Home': nextIndex = 0; break;
      case 'End': nextIndex = tabs.length - 1; break;
      default: return;
    }
    
    e.preventDefault();
    tabs[nextIndex].focus();
    this.activateTab(tabs[nextIndex]);
  }
}

// Usage
new TabManager('.tab-headers');

การปรับแต่งสไตล์ (Variations)

StyleCSS Key Points
Underline Indicatorborder-bottom บน .active + ::after pseudo-element
Background Fillbackground-color เปลี่ยนสีพื้นหลังแท็บ Active
Pill/Roundedborder-radius บนปุ่ม, Container ไม่มี Border Bottom
Minimal/Lineเฉพาะเส้นใต้ Active, ไม่มี Border รอบ Container
Card Styleแต่ละแท็บเป็น Card แยก, Hover Elevate