文件 · Documentation

Tad Carousel
使用說明

現代化、無障礙輪播元件。零依賴、純 JavaScript, 完整支援 WCAG 2.3 AAA 無障礙標準。

v1.0.0 WCAG 2.3 AAA Zero Dependencies MIT License

概覽

Tad Carousel 是一套輕量、無依賴的輪播(Carousel)元件,以無障礙設計為核心, 支援鍵盤操作、觸控拖曳、自動播放、響應式斷點,並遵循 WCAG 2.3 AAA 規範。

元件以 UMD 格式封裝,可在 CommonJS、AMD 或純瀏覽器環境直接使用,不需安裝任何框架或套件管理工具。

主要特色

安裝

方式一:直接引入檔案

將 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';

快速開始

步驟一:撰寫 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 焦點順序規範。

選項參考

基本選項

選項名稱 型別 預設值 說明
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 選項,可依視窗寬度(斷點)設定不同的顯示數量。 物件的 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 張
  }
});

無障礙文字設定

透過 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
  }
});

公開方法

carousel.goTo(index)

前往指定的頁面索引(從 0 起算)

carousel.next()

前往下一頁。若已是最後一頁,依 loop 設定決定是否循環

carousel.prev()

前往上一頁。若已是第一頁,依 loop 設定決定是否循環

carousel.toggleAutoplay()

切換自動播放的暫停/播放狀態

carousel.refresh()

重新計算尺寸與布局,通常在容器尺寸改變後手動呼叫

carousel.destroy()

銷毀輪播實例,移除所有事件監聽器並還原原始 DOM

carousel.getCurrentIndex()

回傳當前顯示的頁面索引(number)

carousel.getPageCount()

回傳總頁數(number)

使用範例

JavaScript

const carousel = new TadCarousel('#carousel');

// 跳至第 3 頁(索引 2)
carousel.goTo(2);

// 取得當前狀態
console.log(carousel.getCurrentIndex()); // 2
console.log(carousel.getPageCount());    // 投影片總數

// 銷毀(例如元件卸載時)
carousel.destroy();

自訂事件

除了回呼函式,元件也會在輪播容器元素上觸發對應的 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} 頁`);
});

鍵盤操作

Tad Carousel 完整支援鍵盤操作,符合 WCAG 2.1.1 鍵盤無障礙要求。

在輪播區域內

←
前往上一頁
→
前往下一頁
Home
跳至第一頁
End
跳至最後一頁

在分頁圓點(Tablist)中

← ↑
選擇上一個圓點
→ ↓
選擇下一個圓點
Home
移至第一個圓點
End
移至最後一個圓點

Tab 焦點遊走順序(autoplay 啟用時)

1
暫停/播放
按鈕
→
2
上一張
按鈕
→
3
投影片內容
可聚焦元素
→
4
下一張
按鈕
→
5
分頁
圓點

① 暫停/播放按鈕為第一個焦點,符合 WCAG 2.4.3 由左至右、由上而下的焦點順序要求。

無障礙設計說明

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 焦點框,不影響滑鼠使用者的視覺體驗。

WCAG 2.4.3 焦點順序修正

指引 2.4:可導覽 — 成功準則 2.4.3 焦點順序(GN1240300E)要求: 互動元件的放置順序應按照內容的序列及關連性,鍵盤焦點遊走順序須符合由左至右、由上而下的邏輯順序。

問題描述

原始版本存在兩個不符合規範的問題:

問題 1 — 容器 tabindex="0" 造成多餘焦點停駐

_setupA11y() 在輪播容器 <div> 上設置 tabindex="0", 使容器本身成為一個無意義的 Tab 停駐點。使用者按 Tab 鍵進入輪播時,第一個焦點落在無操作意義的容器上, 而非第一個可互動的元件。

問題 2 — 暫停/播放按鈕在 DOM 末端,焦點順序錯誤

_buildStructure() 最後才呼叫 _buildAutoplayToggle(), 導致暫停按鈕在 DOM 順序中排在上一張、下一張、所有圓點之後(最後一個焦點)。 此按鈕控制整個輪播的播放狀態,應為進入輪播後的第一個焦點。

問題 3 — 可見投影片未設 tabindex,Tab 鍵跳過圖片內容

_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;
}

修正後的焦點順序

①
暫停/播放
右上角
→
②
投影片
中間內容
→
③
上一張
左側
→
④
下一張
右側
→
⑤
分頁圓點
下方

完整範例

範例一:基本輪播

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)

更新紀錄

v1.0.0 修正版
WCAG 2.4.3 無障礙修正:
  • 移除輪播容器的 tabindex="0",消除多餘焦點停駐點
  • 自動播放暫停/播放按鈕調整為輪播內 DOM 第一個元素,成為鍵盤進入輪播後的第一個焦點
  • CSS 暫停/播放按鈕視覺位置由右下調整為右上,與焦點順序(由上而下)保持一致
  • 可見投影片 <li> 項目加入 tabindex="0",成為焦點順序中的第二停駐點;切換時同步移除舊項目的 tabindex,確立完整焦點序列:暫停→投影片→上一張→下一張→圓點
v1.0.0 初始版本
初始發布。包含基本輪播功能、觸控拖曳、自動播放、響應式支援、WCAG AAA 無障礙基礎實作。