Html 頁籤 效果

Posted by Adam on August 24, 2022
# 有 jQuery UI 使用 jQuery 建立可動態新增且可調整順序(Sortable)的 Tabs 頁面,最穩定的做法是結合 **jQuery UI** 庫。jQuery UI 內建了 `tabs()` 與 `sortable()` 功能,能用極少的代碼達成複雜的互動。 --- ### 1. 快速整合範例 你需要引入 jQuery 與 jQuery UI 的 CSS/JS 檔案。 ```html <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css"> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script> <style> #tabs .ui-tabs-nav { cursor: move; } /* 提示使用者可以拖動 */ .tab-content { padding: 20px; border: 1px solid #ccc; margin-top: -1px; } </style> </head> <body> <div id="tabs"> <ul> <li><a href="#tab-1">首頁</a></li> </ul> <div id="tab-1" class="tab-content"> <p>這是初始頁面。</p> </div> </div> <button id="add-tab" style="margin-top: 20px;">新增頁籤</button> <script> $(function() { // 1. 初始化 Tabs var tabs = $("#tabs").tabs(); // 2. 使頁籤標題可拖動排序 (Sortable) tabs.find(".ui-tabs-nav").sortable({ axis: "x", stop: function() { tabs.tabs("refresh"); // 排序停止後更新 Tabs 狀態 } }); // 3. 動態新增功能 var counter = 2; $("#add-tab").on("click", function() { var label = "頁籤 " + counter; var id = "tab-" + counter; // 新增 li (標題) var li = `<li><a href="#${id}">${label}</a></li>`; tabs.find(".ui-tabs-nav").append(li); // 新增內容區塊 tabs.append(`<div id="${id}" class="tab-content"><p>這是 ${label} 的內容</p></div>`); // 刷新 Tabs 結構並切換到新頁籤 tabs.tabs("refresh"); tabs.tabs("option", "active", -1); // -1 代表最後一個 counter++; }); }); </script> </body> </html> ``` --- ### 2. 核心功能解析 #### **動態新增 (Dynamic Add)** 在 jQuery UI 中,新增 Tab 的邏輯是: 1. 在 `<ul>` 中新增一個 `<li>`。 2. 在容器內新增一個相對應 `id` 的 `<div>`。 3. **關鍵步:** 呼叫 **`tabs("refresh")`**。這會讓 jQuery 重新掃描 DOM,將新加入的 HTML 轉換為功能完整的頁籤。 #### **調整順序 (Sortable)** 這是利用 `tabs.find(".ui-tabs-nav").sortable()`。 * **`axis: "x"`**: 限制只能左右拖動。 * **`refresh`**: 拖動完成後(`stop` 事件),必須通知 Tabs 元件順序已變,否則內部的索引值(Index)可能會錯亂。 --- ### 3. 進階優化建議 * **刪除功能**:如果需要刪除 Tab,可以在 `<li>` 內加入一個 `<span>` 圖示,點擊時移除該 `li` 與對應的 `div`,然後再次執行 `refresh`。 * **資料儲存**:若要記憶順序,可以在 `sortable` 的 `update` 事件中,將目前的 ID 順序存入 `localStorage`。 ### 💡 為什麼選擇 jQuery UI 而非原生 jQuery 手寫? 雖然手寫不難,但 jQuery UI 幫你處理了以下瑣事: 1. **ARIA 無障礙標籤**:自動處理鍵盤左右鍵切換頁籤。 2. **樣式一致性**:自動處理選中狀態(Active Class)。 3. **Sortable 整合**:處理拖動時的占位符(Placeholder)與 DOM 結構保護。 # 無 jQuery UI --- ### 1. 核心邏輯設計 * **動態建立**:使用 `append()` 插入 HTML,並用 `data-target` 屬性將「頁籤」與「內容區」關聯。 * **切換功能**:透過事件委派 `on('click')` 處理點擊。 * **拖拽排序**:利用 HTML5 的 `draggable="true"` 屬性與 `dragstart`、`dragover` 等事件來實作交換位置。 --- ### 2. 完整實作代碼 ```html <!DOCTYPE html> <html> <head> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <style> /* 基本頁籤樣式 */ .tabs-nav { display: flex; list-style: none; padding: 0; border-bottom: 2px solid #ccc; margin: 0; } .tab-item { padding: 10px 20px; cursor: pointer; border: 1px solid #ccc; border-bottom: none; background: #eee; margin-right: 5px; user-select: none; transition: 0.2s; } .tab-item.active { background: white; font-weight: bold; border-top: 3px solid blue; } .tab-item.dragging { opacity: 0.5; border: 2px dashed #000; } .tabs-content { padding: 20px; border: 1px solid #ccc; border-top: none; } .content-panel { display: none; } .content-panel.active { display: block; } </style> </head> <body> <div id="myTabs"> <ul class="tabs-nav" id="tabNav"> <li class="tab-item active" draggable="true" data-target="panel-1">首頁</li> </ul> <div class="tabs-content" id="tabContainer"> <div class="content-panel active" id="panel-1">這是初始頁面內容。</div> </div> </div> <button id="btnAddTab" style="margin-top:20px;">+ 新增頁籤</button> <script> $(document).ready(function() { let tabCount = 1; // --- 1. 動態新增功能 --- $('#btnAddTab').on('click', function() { tabCount++; const tabId = `panel-${tabCount}`; const tabTitle = `頁籤 ${tabCount}`; // 新增標題 $('#tabNav').append(`<li class="tab-item" draggable="true" data-target="${tabId}">${tabTitle}</li>`); // 新增內容 $('#tabContainer').append(`<div class="content-panel" id="${tabId}">這是 ${tabTitle} 的內容</div>`); // 自動切換到新頁籤 $(`[data-target="${tabId}"]`).click(); }); // --- 2. 切換頁籤功能 (事件委派) --- $('#tabNav').on('click', '.tab-item', function() { const target = $(this).data('target'); $('.tab-item').removeClass('active'); $(this).addClass('active'); $('.content-panel').removeClass('active'); $(`#${target}`).addClass('active'); }); // --- 3. 拖拽排序功能 (HTML5 Drag & Drop) --- let dragElement = null; $('#tabNav').on('dragstart', '.tab-item', function(e) { dragElement = this; $(this).addClass('dragging'); }); $('#tabNav').on('dragover', '.tab-item', function(e) { e.preventDefault(); // 必須阻止預設行為才能觸發 drop const overElement = this; if (overElement !== dragElement) { // 判斷拖拽位置,動態交換 DOM 順序 const children = Array.from(this.parentNode.children); const dragIdx = children.indexOf(dragElement); const overIdx = children.indexOf(overElement); if (dragIdx < overIdx) { $(overElement).after(dragElement); } else { $(overElement).before(dragElement); } } }); $('#tabNav').on('dragend', '.tab-item', function() { $(this).removeClass('dragging'); dragElement = null; }); }); </script> </body> </html> ``` --- ### 3. 重點技術說明 #### **HTML5 拖放事件 (Drag and Drop)** 我們使用了三個核心事件: 1. **`dragstart`**: 當使用者開始拖動頁籤時,記錄下被拖動的物件並改變透明度。 2. **`dragover`**: 這是最關鍵的一步。當被拖動的物件經過其他頁籤時,我們直接在 DOM 中使用 `after()` 或 `before()` 移動節點。這樣你不需要重新計算陣列索引,瀏覽器會自動即時更新視覺順序。 3. **`e.preventDefault()`**: 預設情況下,元素是不允許被放置的,必須在 `dragover` 中呼叫此方法才能讓功能運作。 #### **事件委派 (Event Delegation)** 因為頁籤是動態新增的,如果直接寫 `$('.tab-item').click(...)`,新頁籤會沒有反應。所以必須寫成: `$('#tabNav').on('click', '.tab-item', function() { ... })` 這樣無論後來新增多少個頁籤,jQuery 都會透過父元素 `#tabNav` 捕捉到點擊事件。 ### 💡 進階提示:如何儲存順序? 如果你希望使用者重新整理頁面後順序還在,可以在 `dragend` 事件中執行以下邏輯: ```javascript const order = []; $('.tab-item').each(function() { order.push($(this).data('target')); }); localStorage.setItem('tabOrder', JSON.stringify(order)); ```