iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
JavaScript

解決學了JavaScript不知道能做什麼:用DOM操作打造實用網頁效果 系列 第 12 篇

【第十二課】頁籤切換:Tab 頁籤內容切換器(Data Attribute 自訂屬性與狀態控制)

  • 分享至 

  • xImage
  •  

前言

電商網站的商品頁、後台管理系統,甚至個人作品集,隨處可見「頁籤(Tab)切換」功能。

Q:要如何寫出擴充性高、不寫死 ID 的頁籤組件?

A:利用 HTML5 的 data-* 自訂屬性。

核心觀念與語法拆解

  1. data-* 屬性與 dataset:在 HTML 寫入 data-tab="目標ID",JavaScript 可以直接用 element.dataset.tab 讀取資料,達成 HTML 與 JS 的無縫連結。
  2. 排他重置原則 (Reset Pattern):切換狀態時,先用迴圈清除所有按鈕與內容區塊的 .active 類別,最後再單獨幫被點擊的那一組加上 .active,徹底避免多重顯示的錯誤。

觀念與函式深度解析

1. HTML5 data-* 自訂屬性與 element.dataset

  • 為什麼選用這個語法?

    在 HTML 標籤上記錄自訂資料非常方便(例如 <button data-tab="tab-1">)。JavaScript 可以透過 element.dataset.tab 直接取得 "tab-1" 這個字串。這樣就能將按鈕與欲顯示的內容區塊進行一對一關聯,讓 JavaScript 邏輯極度簡潔且具備高度擴充性。

  • 這個語法在幹嘛?

    data-* 允許開發者在 HTML 元素上儲存額外的資訊,dataset 則是 DOM 物件存取這些自訂屬性的專用介面。

  • 有沒有其他替代方案?

    • 方案 A:使用 id 或 class 屬性字串切割
      • 簡介:例如設定 id="tab-btn-1",再用 JS 寫 split('-') 拆解數字。
      • 比較:這樣寫容易造成 Class 或 ID 命名混亂;data-* 是 HTML5 規範中專門用來處理資料關聯的正統做法。

2. 排他思想 (Exclusive Control / Reset Pattern)

  • 為什麼選用這個邏輯?

    切換頁籤的核心邏輯是:「先把所有頁籤的點亮狀態與所有內容區塊隱藏,再把當前被點擊的那一組點亮與顯示」。

  • 這個邏輯在幹嘛?

    透過 forEach 迴圈預先清空(remove('active')),確保畫面上永遠只有一個頁籤處於啟用狀態,避免出現多個頁籤同時顯示的 Bug。

完整實作程式碼

<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <title>第十二課 - Tab 頁籤內容切換器</title>
  <style>
    body { font-family: sans-serif; padding: 40px; background-color: #f8f9fa; }
    .tab-container { max-width: 500px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); overflow: hidden; }

    /* 頁籤按鈕選單區域 */
    .tab-menu { display: flex; background-color: #e9ecef; border-bottom: 2px solid #dee2e6; }
    
    /* 單個按鈕樣式 */
    .tab-btn {
      flex: 1;
      padding: 15px;
      border: none;
      background: none;
      font-size: 16px;
      font-weight: bold;
      color: #6c757d;
      cursor: pointer;
      transition: all 0.2s ease;
    }
    .tab-btn:hover { color: #007bff; }

    /* 按鈕啟用 (Active) 樣式 */
    .tab-btn.active {
      color: #007bff;
      background-color: white;
      border-bottom: 3px solid #007bff;
    }

    /* 頁籤內容容器 */
    .tab-content-wrapper { padding: 25px; }

    /* 單個內容區塊 (預設隱藏) */
    .tab-panel { display: none; }

    /* 內容區塊啟用 (Active) 樣式 */
    .tab-panel.active { display: block; animation: fadeIn 0.3s ease; }

    /* 淡入動畫 */
    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(5px); }
      to { opacity: 1; transform: translateY(0); }
    }
  </style>
</head>
<body>

  <div class="tab-container">
    <!-- 頁籤按鈕選單 (使用 data-tab 綁定目標 Panel 的 ID) -->
    <div class="tab-menu">
      <button class="tab-btn active" data-tab="tab-detail">商品詳情</button>
      <button class="tab-btn" data-tab="tab-specs">規格說明</button>
      <button class="tab-btn" data-tab="tab-reviews">顧客評價</button>
    </div>

    <!-- 頁籤對應內容區塊 -->
    <div class="tab-content-wrapper">
      <div id="tab-detail" class="tab-panel active">
        <h3>商品詳情</h3>
        <p>這款高質感藍牙耳機採用頂級降噪技術,帶來沉浸式的聆聽體驗。續航力高達 30 小時!</p>
      </div>

      <div id="tab-specs" class="tab-panel">
        <h3>規格說明</h3>
        <ul>
          <li>藍牙版本:5.3</li>
          <li>電池容量:500 mAh</li>
          <li>防水等級:IPX5 防潑水</li>
          <li>重量:210 克</li>
        </ul>
      </div>

      <div id="tab-reviews" class="tab-panel">
        <h3>顧客評價 (4.9/5)</h3>
        <p><strong>小明:</strong> 音質非常好,降噪效果驚人!CP 值極高。</p>
        <p><strong>小華:</strong> 配戴非常舒服,戴一整天耳朵也不會痛。</p>
      </div>
    </div>
  </div>

  <script>
    // ----------------------------------------------------
    // JavaScript 邏輯 (data-tab 與動態切換)
    // ----------------------------------------------------

    // 第一步:取得所有的按鈕與所有的內容面板 (Panel)
    const tabBtns = document.querySelectorAll(".tab-btn");
    const tabPanels = document.querySelectorAll(".tab-panel");

    // 第二步:使用 forEach 為每一個按鈕綁定點擊事件
    tabBtns.forEach(function(btn) {
      
      btn.addEventListener("click", function() {
        
        // 1. 取得被點擊按鈕上的 data-tab 屬性值 (對應目標 Panel 的 ID)
        const targetTabId = btn.dataset.tab;

        // 2. 排他思想:移除所有按鈕的 active 類別,並隱藏所有的 Panel
        tabBtns.forEach(b => b.classList.remove("active"));
        tabPanels.forEach(p => p.classList.remove("active"));

        // 3. 點亮當前被點擊的按鈕
        btn.classList.add("active");

        // 4. 找到對應 ID 的 Panel 並加上 active 顯示出來
        const targetPanel = document.getElementById(targetTabId);
        if (targetPanel) {
          targetPanel.classList.add("active");
        }

      });

    });
  </script>
</body>
</html>

總結與實用情境

今天我們學會了如何使用 dataset 來處理 DOM 之間的資料關聯。這種「標籤記資料、JS 讀資料、CSS 控視覺」的三位一體思維,是現代前端開發中最推薦的元件建構方式。

下集預告

我們常常在藝廊網站、電商商品頁看到「點擊小圖,上方大圖與文字說明會跟著改變」的圖片藝廊

—>【第十三課】媒體呈現:動態圖片展示藝廊(Image Gallery & Event Delegation),我們將學習如何運用「事件委派」打造高效能的動態圖片切換組件。


上一篇
【第十一課】動態生成:無窮滾動與卡片動態渲染(createElement 與觸底偵測)
下一篇
【第十三課】媒體呈現:動態圖片展示藝廊(Image Gallery & Event Delegation)
系列文
解決學了JavaScript不知道能做什麼:用DOM操作打造實用網頁效果 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言