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

猜你喜欢

转载自blog.csdn.net/Wangdanting123/article/details/70849352