一般的操作行为是这样的,先Tab键按次序不断focus控件元素,包括链接,按钮,输入框等表单元素或者focus设置了tabindex的普通元素
,处于focus状态元素,浏览器一般会通过虚框或者外发光的形式进行区分和提示,此时我们在按下Enter回车键,就相当于鼠标点击了这个元素,从而可以前往我们想去的目的地,或者执行我们想要的交互效果。
而focus状态元素的标记默认全部都是使用outline属性
我们只要平时注意HTML语义化,例如按钮不要使用,
对于表单元素,如果里面有type为submit类型的按钮,则浏览器天然支持单行输入框的回车提交行为。然而原生的按钮有一个问题,那就是UI样式控制存在兼容性差异,尤其是桌面端网页项目
。可以借助
。
:focus伪类和outline都是IE8浏览器开始支持的。
html:
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;
}
我列表元素信息很多,为了防止视觉干扰,一些操作按钮在鼠标hover当前列表的时候才显示
很多小伙伴在实现的时候,并没有考虑很多,就直接使用display:none隐藏,或者visibility:hidden隐藏,于是会导致隐藏的控件元素压根没法通过键盘让其显示,因为这两种隐藏方式会让元素无法被focus,那该怎么办呢?可以试试使用透明度opacity控制内容的显隐,于是,我们就可以通过:focus伪类让按钮focus时候可见,
html:
栏目1
栏目2
删除
栏目1
栏目2
删除
栏目1
栏目2
删除
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;
}
首先一定要有键盘可访问的触发源,也就是无论是点击区还是hover区域,一定要有个标签,或者原生按钮,或者设置了tabindex的普通元素。
把交互形式和实现原理,分为下面四类:
例如,导航上的二级菜单常使用CSS进行定位,对HTML结构有要求;而搜索的自动下拉提示列表则几乎都使用JS进行定位,列表直接创建于标签下,对HTML结构无依赖。
针对上面四种情况,我需要额外进行的处理分别是:
这些浮层显示的时候,通过上下左右键进行控制
html:
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);