1. 程式人生 > >CSS :focus偽類和JS focus事件提高網站鍵盤可訪問性

CSS :focus偽類和JS focus事件提高網站鍵盤可訪問性

鍵盤訪問網站的常用操作包括:

  • Tab鍵索引控制元件元素;
  • Enter鍵觸發當前處於focus狀態的點選行為;
  • 上下鍵上下滾動網頁;
  • Space空白鍵滾動一屏網頁;
  • Home鍵返回頂部;
  • End鍵滾動到底部;

一般的操作行為是這樣的,先Tab鍵按次序不斷focus控制元件元素,包括連結,按鈕,輸入框等表單元素或者focus設定了tabindex的普通元素,處於focus狀態元素,瀏覽器一般會通過虛框或者外發光的形式進行區分和提示,此時我們在按下Enter回車鍵,就相當於滑鼠點選了這個元素,從而可以前往我們想去的目的地,或者執行我們想要的互動效果。

而focus狀態元素的標記預設全部都是使用outline屬性

我們只要平時注意HTML語義化,例如按鈕不要使用

等標籤,不要重置outline,基本上鍵盤可訪問性就已經及格了。

一、label標籤和表單元素之間的鍵盤可訪問性

對於表單元素,如果裡面有type為submit型別的按鈕,則瀏覽器天然支援單行輸入框的回車提交行為。然而原生的按鈕有一個問題,那就是UI樣式控制存在相容性差異,尤其是桌面端網頁專案可以藉助<label>元素實現按鈕樣式的移花接木

:focus偽類和outline都是IE8瀏覽器開始支援的。

這裡寫圖片描述

這裡寫圖片描述

html:

<form>
<p>使用者名稱:<input></p>
<p>
    <input id="t" type="submit">
    <label class="btn" for="t">提交</label>
</p>
</form>

css:

[type="submit"] {
    position: absolute;
    clip: rect(0 0 0 0);
}
.btn {
    display: inline-block;
    padding: 2px 12px;
    background-color: #cd0000;
    color: #fff;
    font-size: 14px;
    cursor: pointer;
}
:focus + label.btn {
    outline: 1px solid Highlight;
    outline: 5px auto -webkit-focus-ring-color;
}

二、CSS hover顯示隱藏內容的鍵盤可訪問性

我列表元素資訊很多,為了防止視覺干擾,一些操作按鈕在滑鼠hover當前列表的時候才顯示

這裡寫圖片描述

這裡寫圖片描述

很多小夥伴在實現的時候,並沒有考慮很多,就直接使用display:none隱藏,或者visibility:hidden隱藏,於是會導致隱藏的控制元件元素壓根沒法通過鍵盤讓其顯示,因為這兩種隱藏方式會讓元素無法被focus,那該怎麼辦呢?可以試試使用透明度opacity控制內容的顯隱,於是,我們就可以通過:focus偽類讓按鈕focus時候可見,

html:

<table width="300px" border="1">
    <tr>
        <td>欄目1</td>
        <td>欄目2</td>
        <td>
            <a href="https://www.baidu.com" class="btn1">刪除</a>
        </td>
    </tr>
    <tr>
        <td>欄目1</td>
        <td>欄目2</td>
        <td>
            <a href="javascript:;" class="btn1">刪除</a>
        </td>
    </tr>
    <tr>
        <td>欄目1</td>
        <td>欄目2</td>
        <td>
            <a href="javascript:;" class="btn1">刪除</a>
        </td>
    </tr>
</table>

css:

table {
    border-spacing: 0;
}
.btn1 {
    display: inline-block;
    padding: 2px 12px;
    background-color: #cd0000;
    color: #fff;
    font-size: 14px;
    cursor: pointer;
}
tr .btn1 {
    opacity: 0;
    filter: alpha(opacity=0);
}
tr:hover .btn1,
tr .btn1:focus {
    opacity: 1;
    filter: none;
}

三、CSS hover顯示下拉內容的鍵盤可訪問性

首先一定要有鍵盤可訪問的觸發源,也就是無論是點選區還是hover區域,一定要有個<a>標籤,或者原生按鈕,或者設定了tabindex的普通元素。

把互動形式和實現原理,分為下面四類:

  • 列表HTML結構依賴,使用CSS定位,hover顯示;
  • 列表HTML結構依賴,使用CSS定位,click顯示;
  • 列表HTML結構不依賴,使用JS定位,hover顯示;
  • 列表HTML結構不依賴,使用JS定位,click顯示;

例如,導航上的二級選單常使用CSS進行定位,對HTML結構有要求;而搜尋的自動下拉提示列表則幾乎都使用JS進行定位,列表直接創建於標籤下,對HTML結構無依賴。

針對上面四種情況,我需要額外進行的處理分別是:

  • 增加:focus控制;
  • 無需額外處理;
  • 增加JS focus事件處理,處理細節同mouseenter;
  • 無需額外處理;

這些浮層顯示的時候,通過上下左右鍵進行控制

這裡寫圖片描述

html:

<div class="trigger-container">
    <a href="javascript:;" class="trigger" data-target="list">更多操作▾</a>
    <div class="list" id="list">
        <a href="https://www.baidu.com">編輯</a>
        <a href="javascript:;">刪除</a>
    </div>
</div>
<div class="trigger-container">
    <a href="javascript:;" class="trigger" data-target="list1">更多操作▾</a>
    <div class="list" id="list1">
        <a href="javascript:;">編輯</a>
        <a href="javascript:;">刪除</a>
    </div>
</div>

css:

.trigger-container {
float: left;
}
.list {
    position: absolute;
    visibility: hidden;
}
.trigger:hover + .list,
.trigger:focus + .list {
    visibility: visible;
}
.outline {
    outline: 1px solid Highlight;
    outline: 5px auto -webkit-focus-ring-color;
}

js:

(function (doc) {
if (doc.addEventListener) {
    var keycode = {
        37: 'left',
        38: 'up',
        39: 'right',
        40: 'down',
        13: 'enter',
        9: 'tab'
    };
    // 鍵盤高亮類名
    var className = 'outline';
    // 高亮類名的新增與刪除
    var classList = {
        add: function (ele) {
            ele.className = ele.className + ' ' + className;
        },
        remove: function (ele) {
            ele.className = ele.className.split(/\s+/).filter(function (cl) {
                if (cl != className) {
                    return cl;    
                }
            }).join(' ');
        },
        removeAll: function () {
            [].slice.call(doc.querySelectorAll('.' + className)).forEach(function (ele) {
                classList.remove(ele);
            });
        },
        has: function (ele) {
            return ele.className.split(/\s+/).filter(function (cl) {
                if (cl == className) {
                    return cl;    
                }
            }).length > 0;
        }
    };

    //鍵盤事件
    doc.addEventListener('keydown', function (event) {
        // 是否是上下左右鍵
        var direction = keycode[event.keyCode];
        if (!direction) {
            return;    
        }
        if (direction == 'tab') {
            classList.removeAll();
            return;
        }
        // 當前啟用元素
        var trigger = doc.activeElement;
        if (!trigger) {
            return;
        }
        // 對應的面板
        var attrTarget = trigger.getAttribute('target') || trigger.getAttribute('data-target');
        var target = attrTarget && doc.getElementById(attrTarget);
        if (!target) {
            return;    
        }
        // 需要是顯示狀態
        if (target.clientWidth == 0 && target.clientHeight == 0) {
            return;
        }
        // 如果是回車事件
        if (direction == 'enter') {
            var eleFocus = target.querySelector('.' + className);
            if (eleFocus) {
                // 阻止預設的回車
                event.preventDefault();
                eleFocus.click();
                return;
            }
        }
        // 如果都符合,同時有目標子元素
        var arrEleFocusable = target.storeFocusableEle, index = target.storeIndexFocus;
        if (!arrEleFocusable) {
            arrEleFocusable = [].slice.call(target.querySelectorAll('a[href], button:not(:disabled), input:not(:disabled)'));
            target.storeFocusableEle = arrEleFocusable;
            target.storeIndexFocus = -1;
            index = -1;
        }
        if (arrEleFocusable.length == 0) {
            return;    
        }
        // 先全部清除focus態
        arrEleFocusable.forEach(function (ele) {
            classList.remove(ele);
        });
        // 阻止預設的上下鍵滾屏
        event.preventDefault();
        // 索引加加減減
        if (direction == 'left' || direction == 'up') {
            index--;
            if (index < 0) {
                index = -1;
            }
        } else if (direction == 'right' || direction == 'down') {
            index++;
            if (index > arrEleFocusable.length - 1) {
                index = arrEleFocusable.length;
            }
        }
        // 如果有對應的索引元素
        if (arrEleFocusable[index]) {
            // 高亮對應的控制元件元素
            classList.add(arrEleFocusable[index]);
        }
        // 記錄索引
        target.storeIndexFocus = index;
    });

    doc.addEventListener('mousedown', function (event) {
        var target = event.target;
        if (target && !classList.has(target)) {
            classList.removeAll();
        }
    });
}    
})(document);