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);