文件 · Documentation
Tad Carousel
使用說明
現代化、無障礙輪播元件。零依賴、純 JavaScript, 完整支援 WCAG 2.3 AAA 無障礙標準。
Overview
概覽
Tad Carousel 是一套輕量、無依賴的輪播(Carousel)元件,以無障礙設計為核心, 支援鍵盤操作、觸控拖曳、自動播放、響應式斷點,並遵循 WCAG 2.3 AAA 規範。
元件以 UMD 格式封裝,可在 CommonJS、AMD 或純瀏覽器環境直接使用,不需安裝任何框架或套件管理工具。
主要特色
- ✦ 零外部依賴,檔案體積極小
- ✦ 完整 WCAG 2.3 AAA 無障礙支援
- ✦ 觸控與滑鼠拖曳支援
- ✦ 響應式斷點設定
- ✦ 深色模式、高對比模式自動適應
- ✦ 減少動態效果(prefers-reduced-motion)
Installation
安裝
方式一:直接引入檔案
將 tad_carousel.css 與 tad_carousel.js 放置於專案目錄,然後在 HTML 中引入:
HTML
<head> <!-- 引入樣式 --> <link rel="stylesheet" href="tad_carousel.css"> </head> <body> <!-- 頁面內容 --> <!-- 在 </body> 前引入腳本 --> <script src="tad_carousel.js"></script> </body>
方式二:CommonJS(Node.js / Webpack)
JavaScript
const TadCarousel = require('./tad_carousel');
方式三:ES Module(搭配打包工具)
JavaScript
import TadCarousel from './tad_carousel';
Quick Start
快速開始
步驟一:撰寫 HTML 結構
在容器元素內直接放置輪播項目,元件將自動包裝為正確的 DOM 結構。
HTML
<div id="myCarousel"> <div><img src="slide1.jpg" alt="第一張投影片說明"></div> <div><img src="slide2.jpg" alt="第二張投影片說明"></div> <div><img src="slide3.jpg" alt="第三張投影片說明"></div> </div>
步驟二:初始化元件
JavaScript
const carousel = new TadCarousel('#myCarousel', { items: 1, nav: true, dots: true });
帶自動播放的基本範例
JavaScript
const carousel = new TadCarousel('#myCarousel', { items: 1, autoplay: true, // 啟用自動播放 autoplayTimeout: 4000, // 每 4 秒切換 loop: true, // 循環播放 nav: true, dots: true });
啟用 autoplay: true 時,請確保使用最新修正版的 tad_carousel.js(v1.0.0 修正版),
暫停/播放按鈕將自動被設為輪播內第一個焦點位置,符合 WCAG 2.4.3 焦點順序規範。
Configuration
選項參考
基本選項
| 選項名稱 | 型別 | 預設值 | 說明 |
|---|---|---|---|
items |
number | 1 |
每頁同時顯示的投影片數量 |
gap |
number | 16 |
投影片之間的間距(單位:px) |
loop |
boolean | false |
到達末端時是否循環回到開頭 |
startIndex |
number | 0 |
初始顯示的頁面索引(從 0 起算) |
rtl |
boolean | false |
啟用從右到左(RTL)方向 |
導航與指示器
| 選項名稱 | 型別 | 預設值 | 說明 |
|---|---|---|---|
nav |
boolean | true |
顯示上一張/下一張導航按鈕 |
dots |
boolean | true |
顯示分頁圓點指示器 |
自動播放
| 選項名稱 | 型別 | 預設值 | 說明 |
|---|---|---|---|
autoplay |
boolean | false |
啟用自動播放功能。啟用後將顯示暫停/播放按鈕(符合 WCAG 2.2.2) |
autoplayTimeout |
number | 5000 |
自動播放切換間隔(毫秒) |
autoplayPauseOnHover |
boolean | true |
滑鼠懸停或焦點進入時自動暫停播放 |
拖曳
| 選項名稱 | 型別 | 預設值 | 說明 |
|---|---|---|---|
touchDrag |
boolean | true |
啟用觸控拖曳切換 |
mouseDrag |
boolean | true |
啟用滑鼠拖曳切換 |
dragThreshold |
number | 50 |
觸發切換所需的最小拖曳距離(px) |
回呼函式
| 選項名稱 | 型別 | 說明 |
|---|---|---|
onInit |
function | 元件初始化完成時呼叫 |
onChange |
function | 投影片開始切換時呼叫。傳入 { from, to } |
onChanged |
function | 投影片切換動畫結束後呼叫。傳入 { index } |
onDrag |
function | 開始拖曳時呼叫 |
onDragged |
function | 拖曳結束時呼叫 |
Responsive
響應式設定
透過 responsive 選項,可依視窗寬度(斷點)設定不同的顯示數量。
物件的 key 為最大寬度(px),value 為該斷點的設定覆蓋值。
JavaScript
new TadCarousel('#carousel', { items: 4, // 桌機顯示 4 張 responsive: { 1024: { items: 3 }, // ≤ 1024px 顯示 3 張 768: { items: 2 }, // ≤ 768px 顯示 2 張 480: { items: 1 } // ≤ 480px 顯示 1 張 } });
Accessibility Text
無障礙文字設定
透過 a11y 選項可自訂所有提供給輔助技術(螢幕閱讀器)的文字說明。
%d 為當前編號佔位符,%t 為總數佔位符。
JavaScript
new TadCarousel('#carousel', { a11y: { prevText: '上一張', // 上一張按鈕的 aria-label nextText: '下一張', // 下一張按鈕的 aria-label dotText: '前往第 %d 張', // 圓點按鈕的 aria-label slideText: '第 %d 張,共 %t 張', // 投影片的 aria-label pauseText: '暫停自動播放', // 暫停按鈕的 aria-label playText: '開始自動播放', // 播放按鈕的 aria-label carouselLabel: '輪播區域' // 輪播容器的 aria-label } });
API
公開方法
前往指定的頁面索引(從 0 起算)
前往下一頁。若已是最後一頁,依 loop 設定決定是否循環
前往上一頁。若已是第一頁,依 loop 設定決定是否循環
切換自動播放的暫停/播放狀態
重新計算尺寸與布局,通常在容器尺寸改變後手動呼叫
銷毀輪播實例,移除所有事件監聽器並還原原始 DOM
回傳當前顯示的頁面索引(number)
回傳總頁數(number)
使用範例
JavaScript
const carousel = new TadCarousel('#carousel'); // 跳至第 3 頁(索引 2) carousel.goTo(2); // 取得當前狀態 console.log(carousel.getCurrentIndex()); // 2 console.log(carousel.getPageCount()); // 投影片總數 // 銷毀(例如元件卸載時) carousel.destroy();
Events
自訂事件
除了回呼函式,元件也會在輪播容器元素上觸發對應的 CustomEvent,
可使用 addEventListener 監聽。
| 事件名稱 | 觸發時機 | detail 屬性 |
|---|---|---|
tadcarousel:oninit |
元件初始化完成 | { carousel } |
tadcarousel:onchange |
投影片開始切換 | { carousel, from, to } |
tadcarousel:onchanged |
切換動畫結束 | { carousel, index } |
tadcarousel:ondrag |
開始拖曳 | { carousel } |
tadcarousel:ondragged |
拖曳結束 | { carousel } |
JavaScript — 監聽自訂事件
const el = document.querySelector('#carousel'); el.addEventListener('tadcarousel:onchanged', (e) => { const { index } = e.detail; console.log(`切換至第 ${index + 1} 頁`); });
Keyboard
鍵盤操作
Tad Carousel 完整支援鍵盤操作,符合 WCAG 2.1.1 鍵盤無障礙要求。
在輪播區域內
在分頁圓點(Tablist)中
Tab 焦點遊走順序(autoplay 啟用時)
按鈕
按鈕
可聚焦元素
按鈕
圓點
① 暫停/播放按鈕為第一個焦點,符合 WCAG 2.4.3 由左至右、由上而下的焦點順序要求。
Accessibility
無障礙設計說明
ARIA 結構
輪播容器被標記為 role="region" 搭配 aria-roledescription="輪播",讓螢幕閱讀器能正確識別此為輪播區塊。
每個投影片具備 role="group" 與 aria-roledescription="投影片",並以 aria-label 標示序號(如「第 1 張,共 4 張」)。
目前不可見的投影片會設置 aria-hidden="true" 與 inert 屬性,使其對輔助技術完全隱藏,並阻止鍵盤焦點進入。
即時播報區域(Live Region)
元件內含一個 aria-live 區域,在投影片切換時播報當前頁面資訊(如「第 2 頁,共 4 頁」),確保螢幕閱讀器使用者能即時得知狀態變化。
分頁指示器標示
每個圓點指示器為一般按鈕,並以 aria-current="true" 標示當前選取狀態。非作用中的圓點設置 tabindex="-1",避免產生多餘的 Tab 停駐點。
其他無障礙考量
prefers-reduced-motion:系統開啟「減少動態效果」時,切換動畫將自動停用。
prefers-color-scheme:深色模式下自動切換為高可讀性的配色方案。
prefers-contrast:高對比模式下按鈕與指示器使用純黑/純白配色。
focus-visible:所有互動元件以 :focus-visible 顯示清晰的 3px 焦點框,不影響滑鼠使用者的視覺體驗。
Bug Fix · WCAG 2.4.3
WCAG 2.4.3 焦點順序修正
指引 2.4:可導覽 — 成功準則 2.4.3 焦點順序(GN1240300E)要求: 互動元件的放置順序應按照內容的序列及關連性,鍵盤焦點遊走順序須符合由左至右、由上而下的邏輯順序。
問題描述
原始版本存在兩個不符合規範的問題:
_setupA11y() 在輪播容器 <div> 上設置 tabindex="0",
使容器本身成為一個無意義的 Tab 停駐點。使用者按 Tab 鍵進入輪播時,第一個焦點落在無操作意義的容器上,
而非第一個可互動的元件。
_buildStructure() 最後才呼叫 _buildAutoplayToggle(),
導致暫停按鈕在 DOM 順序中排在上一張、下一張、所有圓點之後(最後一個焦點)。
此按鈕控制整個輪播的播放狀態,應為進入輪播後的第一個焦點。
_updateItemsState() 僅透過 inert 隱藏不可見項目,
但可見投影片的 <li> 項目從未設置 tabindex。
若投影片內容為純圖片(無連結),Tab 鍵會直接從暫停按鈕跳至上一張按鈕,
使「投影片」從焦點序列中消失,不符合「由上而下、左至右」的預期順序
(暫停→投影片→上一張→下一張→圓點)。
修正方式
修正 1 — _setupA11y() 移除容器 tabindex
// ❌ 原始程式碼 if (!element.hasAttribute('tabindex')) { element.setAttribute('tabindex', '0'); } // ✅ 修正後:移除容器 tabindex,焦點應由子元件接管 element.removeAttribute('tabindex');
修正 2 — _buildStructure() 暫停鈕優先插入 DOM,軌道次之,導航鍵最後
// ❌ 原始程式碼(autoplayToggle 最後建立) trackContainer.appendChild(track); element.appendChild(trackContainer); if (options.nav) this._buildNav(); if (options.dots) this._buildDots(); if (options.autoplay) this._buildAutoplayToggle(); // ← 最後,焦點排末位 // ✅ 修正後:① autoplayToggle 最先 → ② trackContainer(投影片)→ ③ nav(上一張/下一張)→ ④ dots if (options.autoplay) this._buildAutoplayToggle(); // ← ① 暫停/播放,焦點排首位 trackContainer.appendChild(track); element.appendChild(trackContainer); // ← ② 投影片(可見項目設 tabindex="0") if (options.nav) this._buildNav(); // ← ③ 上一張 → 下一張 if (options.dots) this._buildDots(); // ← ④ 分頁圓點
修正 4 — _updateItemsState() 可見投影片加入 tabindex="0"
// ❌ 原始程式碼(投影片項目從未設 tabindex,Tab 鍵直接跳過投影片) if (!isVisible) { item.setAttribute('inert', ''); } else { item.removeAttribute('inert'); } // ✅ 修正後:可見投影片設 tabindex="0",成為焦點順序中的 ② 投影片 停駐點 if (!isVisible) { item.setAttribute('inert', ''); item.removeAttribute('tabindex'); // 隱藏項目不可聚焦 } else { item.removeAttribute('inert'); item.setAttribute('tabindex', '0'); // 可見項目可聚焦 }
修正 3 — tad_carousel.css 視覺位置配合調整
/* ❌ 原始:按鈕位於右下角,與「由上而下」邏輯相矛盾 */ .tad-carousel__autoplay-toggle { bottom: 0.5rem; right: 0.5rem; } /* ✅ 修正:按鈕移至右上角,視覺位置與焦點順序一致 */ .tad-carousel__autoplay-toggle { top: 0.5rem; right: 0.5rem; }
修正後的焦點順序
右上角
中間內容
左側
右側
下方
Examples
完整範例
範例一:基本輪播
HTML + JavaScript
<div id="carousel-basic"> <div>投影片一</div> <div>投影片二</div> <div>投影片三</div> </div> <script> new TadCarousel('#carousel-basic', { items: 1, nav: true, dots: true, loop: true }); </script>
範例二:自動播放(WCAG 修正版)
HTML + JavaScript
<div id="carousel-auto"> <div>投影片一</div> <div>投影片二</div> <div>投影片三</div> </div> <script> // autoplay: true 時,暫停/播放鈕自動成為第一個焦點(WCAG 2.4.3) new TadCarousel('#carousel-auto', { autoplay: true, autoplayTimeout: 4000, autoplayPauseOnHover: true, loop: true, nav: true, dots: true }); </script>
範例三:多張顯示 + 響應式
JavaScript
new TadCarousel('#carousel-multi', { items: 3, gap: 20, nav: true, dots: true, responsive: { 1024: { items: 2 }, 600: { items: 1 } } });
範例四:事件監聽
JavaScript
const carousel = new TadCarousel('#carousel', { onChange: (e) => console.log(`切換中: ${e.from} → ${e.to}`), onChanged: (e) => console.log(`已切換至: ${e.index}`) }); // 也可透過 CustomEvent 監聽 document.querySelector('#carousel') .addEventListener('tadcarousel:onchanged', (e) => { updateExternalCounter(e.detail.index); });
範例五:程式化控制
JavaScript
const carousel = new TadCarousel('#carousel'); // 外部按鈕控制 document.querySelector('#btnNext') .addEventListener('click', () => carousel.next()); document.querySelector('#btnPrev') .addEventListener('click', () => carousel.prev()); // 動態跳至特定頁 carousel.goTo(2); // 跳至第 3 頁(index = 2)
Changelog
更新紀錄
- 移除輪播容器的
tabindex="0",消除多餘焦點停駐點 - 自動播放暫停/播放按鈕調整為輪播內 DOM 第一個元素,成為鍵盤進入輪播後的第一個焦點
- CSS 暫停/播放按鈕視覺位置由右下調整為右上,與焦點順序(由上而下)保持一致
- 可見投影片
<li>項目加入tabindex="0",成為焦點順序中的第二停駐點;切換時同步移除舊項目的tabindex,確立完整焦點序列:暫停→投影片→上一張→下一張→圓點