iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 12
1

這系列無障礙的鐵人賽文章,實踐的內容主要是根據 W3C:WAI-ARIA 的實踐,從設計模式及組件(Design Patterns and Widgets)裡面挑選最想嘗試的,如果有朋友想瞭解全部 Widget 該怎麼實作及其規範,歡迎自行爬規範內容,也許我們可以討論一下;若以下文章內容理解有任何錯誤,請多指教~

(圖片來源:unsplash

手風琴 Accordion(打開文件

3.1 Accordion
An accordion is a vertically stacked set of interactive headings that each contain a title, content snippet, or thumbnail representing a section of content. The headings function as controls that enable users to reveal or hide their associated sections of content. Accordions are commonly used to reduce the need to scroll when presenting multiple sections of content on a single page.

我們在網頁實務上也算蠻常使用手風琴這個組件的,通常用減少於頁面上多個區塊顯示內容過長的捲動,它是一組垂直堆疊、可操作的標題,每個標題的關聯內容可以自定為折疊、不折疊,裡面包含一個小標、內容或能呈現意象的縮圖。使用者透過與標題的互動,可以顯示或隱藏其關聯的內容。

標題容器|Accordion Header

提供給使用者用滑鼠點選、用鍵盤操作的標題區塊。

內容容器|Accordion Panel

與各個標題區塊,各自相關的內容。

我的第一個手風琴是在 W3Schools 學習刻出來的!

以前都不知道 outline: none; 其實是不好的做法,我用得很爽啊啊啊。(大驚)


鍵盤的可訪問性

  • Enter 或 Space(必要)
    • 顯示區塊相關內容(Accordion Panel)
  • Tab(必要)
    • 能 Focus,從第一個標題到下一個標題(與 Dom 正序)
  • Shift Tab(必要)
    • 能 Focus,從最後一個標題到第一個標題(與 Dom 反序)

更多選擇性的按鍵操控,請閱讀文件。


Roles、States、Properties

  • 每一個 Accordion Header 中的「標題名稱」需要使用角色是 button 的元素當容器,也就是說原生語義標籤已經是 button 的不用管它,但是非語義標籤需要新增 role="button"
  • 包裹「標題名稱」的 button 每個可摺疊標題按鈕都包裝在一個元素角色為 headings 的標籤,加上 aria-level="層級" ,層級的值要符合於頁面架構。
    • 如果使用原生的標題標籤 <h3>,那麼已經含有 aria-level 的意義,就不用再設定 aria-level="3"
    • headings 角色的元素裡,只能有一個 button 角色的元素。如果還有其他一定要呈現的視覺內容,就放在 headings 之外。
  • headings > button
    • 當手風琴的某個區塊內容是顯示的, button 加上 aria-expanded="true"
    • 反之,隱藏的話,加上aria-expanded="false"
    • 加上 aria-controls,值是 Accordion Panel 容器的 id
  • 當前顯示的區塊內容,而功能上「不允許使用者點擊 button 去隱藏當前顯示的內容」,如果有這樣的需求,就在 button 上加上 aria-disabled="true"
  • 最後這一點是選擇性的,看你要不要將「關聯內容 panel 的容器」加上 role="region",再加上 aria-labelledby,值為 headings > buttonid
    • 角色 region 對於螢幕閱讀器理解結構是很有幫助的。
    • 不過不要把 region 用在你同時展開六個以上區塊內容的時候,這樣很混肴。換句話說,要用的話,你可以做一個六個區塊的手風琴,但一次只顯示一個區塊內容。

實踐開始囉!

希望能做到以下兩點:

  • [x] 鍵盤可操作
  • [x] 支援螢幕閱讀器

HTML

  • 大原則:能使用原生含有語義的標籤(標題、超連結、按鈕、表單元素),就不要使用無語義的標籤再來幫它加上 ARIA 屬性。
    • 如果想使用 HTML5 的標籤 <summary><details> ,因為支援度低, IE 6~11、Edge 較多人使用的版本不支援,所以還是採取以下做法。
    • 實作沒注意的話,會將標題內容用 <div> 包裹,這裡直接使用 <button>,保持鍵盤可操作、螢幕閱讀器可念出包裹的標題名稱與原生語義 Role,樣式什麼的就交給 CSS 吧。
    <div class="accordion">
        <button class="accordion__header" aria-controls="panel-1">
            <h3 class="accordion__header__title">
                關於我
            </h3>
        </button>
        <div class="accordion__panel" id="panel-1" role="region">
           <p class="accordion__panel__content">我是 Askie Lin,目前鑽研前端領域中。我喜歡記錄我學習過的事物,也因此有可能出現錯誤的地方,如有文章不足之處,歡迎大家在我的文章底下留言告訴我唷,謝謝!</p>
        </div>
        <button class="accordion__header" aria-controls="panel-2">
            <h3 class="accordion__header__title">
                一起實踐無障礙吧
            </h3>
        </button>
        <div class="accordion__panel" id="panel-2">
           <p class="accordion__panel__content">還是無障礙的新手,等級一啊.....</p>
        </div>
    </div>

CSS

  • 主要就是設定當手風琴打開及關閉的樣式。
    .accordion {
        box-sizing: border-box;
        margin: 10px;
        max-width: 320px;
    
        &__header {
            display: block;
            position: relative;
            background: #ecf3f2;
            border: 0;
            width: 100%;
            text-align: left;
            padding: 0.5rem 15px;
            border: 1px solid #999;
            color: #333;
            
            &:first-child {
                border-bottom: 0;
            }
            
            &:hover {
                cursor: pointer;
            }
            
            &:after {
                content: '';
                border: .4em solid transparent;
                border-left: .5em solid #222;
                bottom: 0;
                height: 0;
                margin: auto;
                position: absolute;
                right: 1rem;
                top: 0;
                transition: transform .2s ease-in-out;
                transform-origin: center center;
                transform: rotate(0deg);
                width: 0;
            }
            
            &__title {
                font-size: 1.5rem;
            }
            
            &.active {
                background: #00a988;
                color: #fff;
                border: 1px solid #00a988;
                
                &:after {
                    border-left-color: #fff;
                    transform: rotate(90deg);
                    right: 1.25rem;
                    top: 5px;
                }
                
                + .accordion__panel {
                    display: block;
                }
            }
            
        }
    
        &__panel {
            padding: 0 15px;
            border-left: 2px solid #ecf3f2;
            border-right: 2px solid #ecf3f2;
            max-height: 0;
            transition: max-height .2s ease-in-out, padding .2s ease-in-out;
            display: none;
            
            &[aria-hidden="false"] {
                max-height: 100vh;
                padding: 1em 15px 1.5em;
                display: block;
            }
            
            &:last-child {
                border-bottom: 2px solid #ecf3f2;
            }
            
            &__content {
                font-size: 1.15rem;    
                line-height: 1.5;
                color: #333;
            }
        }
    }

JavaScript

    const accordionButtons = document.querySelectorAll('.accordion__header');
    const defaultActiveButton = document.querySelector('.accordion__header.active');
    
    const openOneAccordion = (header) => {
        accordionButtons.forEach(button => {
            button.classList.remove('active');
            button.removeAttribute('role');
            button.setAttribute('aria-expanded', false);
            button.nextElementSibling.setAttribute('aria-hidden', true);
        });
        
        header.classList.add('active');
        header.setAttribute('role', 'region');
        header.setAttribute('aria-expanded', true);
        header.nextElementSibling.setAttribute('aria-hidden', false);   
    }
    
    /* 初始化預設開啟的按鈕 */
    if(defaultActiveButton){
        openOneAccordion(defaultActiveButton);
    }
    
    
    /* 監聽每個標題 */
    accordionButtons.forEach(button => {
        button.addEventListener('click', () => {    
            openOneAccordion(button);
        })
    });

可以打開 Codepen 看動起來的樣子,用鍵盤操作看看。


Reference


上一篇
以設計層面來思考無障礙網站
下一篇
實作無障礙網頁功能:當使用者輸入錯誤的警告視窗 Alert
系列文
實踐無障礙網頁設計(Web Accessibility)30

尚未有邦友留言

立即登入留言