厚着脸作推广,我的网站 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)。