厚着臉作推廣,我的網站 http://www.wemlion.com/。javascript
本文轉載自:衆成翻譯
譯者:文藺
連接:http://www.zcfy.cc/article/1179
原文:https://www.christianheilmann.com/2016/08/15/better-keyboard-navigation-with-progressive-enhancement/css
建立界面時很重要的一點是,要考慮到那些只依賴鍵盤來使用產品的用戶。這對可訪問性來講是基本要求,在多數狀況下,經過鍵盤操做訪問也並不是難事。這意味着首先,也是最重要的,是使用鍵盤可訪問元素進行交互。html
若是但願用戶跳轉到其餘地方,使用帶有有效的 href 屬性的錨點鏈接java
若是但願用戶執行你本身的代碼,並在當前文檔中停留,使用按鈕git
經過流動 tabIndex 技術幾乎可使全部內容都能經過鍵盤訪問,不過,既然已經有 HTML 元素能夠作一樣的事情,又何須再麻煩呢。github
不過,使用恰當的元素並不那麼簡單;用戶鍵盤在元素集合中所處的位置,也要顯眼一些。給激活的元素加上輪廓(outline),瀏覽器解決了這個問題。這雖然超有用,但倒是一些人的眼中釘,他們但願由本身控制全部交互的視覺展示。在 CSS 中將 outline 屬性設置爲 none,就能移除這個視覺輔助功能;不過這會帶來不小的可訪問性問題,除非你提供一個別的替代。瀏覽器
使用最顯眼的 HTML 元素;加上一些 CSS,確保除 hover 以外 focus 狀態一樣也被定義。這樣就可使用戶在列表中的一個個項目間,輕鬆地經過 tab 來切換了。Shift + Tab 容許回退。能夠看下 demo,HTML 挺簡單粗暴的。緩存
<ul> <li><button>1</button></li> <li><button>2</button></li> <li><button>3</button></li> … <li><button>20</button></li> </ul>
使用列表,爲咱們的元素賦予了層次結構,以及普通瀏覽器所沒有的可訪問性技術的導航方式。它還帶來不少 HTML 元素,咱們能夠本身添加樣式。經過一點樣式,咱們能夠將其轉換爲網格,佔用更少的垂直空間,容納更多內容。網站
ul, li { margin: 0; padding: 0; list-style: none; } button { border: none; display: block; background: goldenrod; color: white; width: 90%; height: 30px; margin: 5%; transform: scale(0.8); transition: 300ms; } button:hover, button:focus { transform: scale(1); outline: none; background: powderblue; color: #333; } li { float: left; } /* grid magic by @heydonworks https://codepen.io/heydon/pen/bcdrl */ li { width: calc(100% / 4); } li:nth-child(4n+1):nth-last-child(1) { width: 100%; } li:nth-child(4n+1):nth-last-child(1) ~ li { width: 100%; } li:nth-child(4n+1):nth-last-child(2) { width: 50%; } li:nth-child(4n+1):nth-last-child(2) ~ li { width: 50%; } li:nth-child(4n+1):nth-last-child(3) { width: calc(100% / 4); } li:nth-child(4n+1):nth-last-child(3) ~ li { width: calc(100% / 4); }
結果看起來很是棒,在查看列表的過程當中,咱們能清楚地看到本身所處的位置。spa
不過,在訪問網格時,經過鍵盤進行兩個方向的移動會不會更好呢?
使用一點 JavaScript 作漸進加強,咱們作到了,可使用鼠標或方向鍵訪問網格。
不過記着,這僅僅只是一個加強。假設 JavaScript 由於各類可能的緣由執行失敗,依然能夠經過 tab 來訪問列表,咱們失去的只是便利,但至少還有可用的界面。
我將這個打包成了一個小巧、無依賴的開源 JavaScript 項目 gridnav,能夠在 GitHub 上獲取代碼。你要作的就是調用腳本,傳給它一個選擇器以獲取元素列表。
<ul id="links" data-amount="5" data-element="a"> <li><a href="#">1</a></li> <li><a href="#">2</a></li> … <li><a href="#">25</a></li> </ul> <script src="gridnav.js"></script> <script> var linklist = new Gridnav('#links'); </script>
經過列表元素的 data- 屬性,能夠本身定義每行元素的數量以及鍵盤可訪問的元素。這些是可選的,但設置以後會讓代碼更快,出錯可能性更小。README 文件更詳細地解釋瞭如何使用。
開始考慮如何作的時候,像任何開發者同樣,抓到了最複雜的方式。我覺得,須要對父節點、兄弟節點的大量定位比較,使用上 getBoundingClientRect,進行大量的 DOM 訪問。
以後我往回走了一步,意識到如何展現列表並不重要。最終不過是一個列表,咱們要訪問它而已。甚至不須要訪問 DOM,由於咱們所作的不過是從一堆按鈕或錨點鏈接中的一個切換到另外一個。咱們要作的就是:
找到當前所在元素(event.target)。
獲取按下的鍵。
根據鍵向前向後移動,或跳過一些元素到下一行。
就像這樣(點擊這裏試試看):
咱們須要跳過的元素數量是由每行的元素數量決定的。向上等同於向前 n 個元素,向下至關於向後 n 個元素。
使用一些小技巧,完整代碼很是簡短:
(function(){ var list = document.querySelector('ul'); var items = list.querySelectorAll('button'); var amount = Math.floor( list.offsetWidth / list.firstElementChild.offsetWidth ); var codes = { 38: -amount, 40: amount, 39: 1, 37: -1 }; for (var i = 0; i < items.length; i++) { items[i].index = i; } function handlekeys(ev) { var keycode = ev.keyCode; if (codes[keycode]) { var t = ev.target; if (t.index !== undefined) { if (items[t.index + codes[keycode]]) { items[t.index + codes[keycode]].focus(); } } } } list.addEventListener('keyup', handlekeys); })();
這裏發生了什麼?
首先咱們獲取到了列表元素,並緩存全部可經過鍵盤訪問的元素:
var list = document.querySelector('ul'); var items = list.querySelectorAll('button');
計算每次上下移動須要跳過的元素數量,將列表的寬度除以列表第一個子元素(本例中是 LI)的寬度便可:
var amount = Math.floor( list.offsetWidth / list.firstElementChild.offsetWidth );
相較於 switch 語句或者大量的 if 判斷,我更樂意使用查找表。在本例總共,查找表名字是 codes。向上鍵值爲 38,向下 40,向左 37,向右 39。假如咱們拿到了 codes[37],值爲 -1,也就是咱們要在列表中移動的數量:
var codes = { 38: -amount, 40: amount, 39: 1, 37: -1 };
可使用 event.target 獲取按下鍵盤時列表中的選中元素,但咱們不知道它在列表中的位置。爲避免重複遍歷列表,一次性遍歷全部按鈕,將它們在列表中的索引存儲在按鈕自身的 index 屬性中。
for (var i = 0; i < items.length; i++) { items[i].index = i; }
handlekeys() 完成剩餘工做。讀取所按按鍵的鍵值,而後到 codes 中查找。因此,咱們只針對方向鍵作出響應。接着獲取當前的元素,檢查其是否有 index 屬性。若是有,則檢查咱們將要移到的位置是否有元素存在。若是元素存在,則得到焦點。
function handlekeys(ev) { var keycode = ev.keyCode; if (codes[keycode]) { var t = ev.target; if (t.index !== undefined) { if (items[t.index + codes[keycode]]) { items[t.index + codes[keycode]].focus(); } } } }
給列表綁定一個 keyup 事件監聽器,搞定 :)
list.addEventListener('keyup', handlekeys);
若是你想看真實效果,這有一個講述各個細節的快速視頻教程。
視頻在最後的代碼部分有點 bug,由於我沒將 count 屬性和 undefined 對比,因此在第一個元素上,鍵盤功能無法正常工做(0 是 falsy)。