使用Aria-modal属性
在弹窗元素上增加属性:
<section id="alert-dialog" role="dialog" aria-modal="true" aria-label="xx对话框" tabindex="-1">
<h1>标题内容</h1>
<button>关闭</button>
</section>
role="dialog" ——让浏览器告诉屏幕阅读器一个对话框打开了aria-modal="true" ——对话框以外的元素无法被聚焦(android上不生效)tabindex="-1 ——让对话框可以聚焦但无法被tab访问(android上必须加)。HTML dialog 元素,不能使用tabindex属性aria-label="" 或 aria-labelledby="xx元素id" ——指定元素朗读的内容
- 这里按理说无需加tabindex属性,但实际实验中发现无tabindex不能朗读,可以加上试试
- 弹窗div加上tabindex属性后,内部需要聚焦的第一个元素也要加上tabindex属性
Android不生效
解决焦点问题
document.activeElement可以获取到当前聚焦的元素
聚焦
使用a链接或直接改hash
原理是用锚点来指定位置。会导致在浏览器会话历史中新增一条记录,需要在关闭弹窗的时候history.back()或者history.go(-1)
<a @click.stop.prevent="showSelfAlert3"
role="button"
target="_self"
href="#alert-dialog"
>
打开弹窗按钮
</a>
<div role="dialog" airia-live="polite" aria-label="xx对话框" id="alert-dialog" >弹窗内容</div>
或者使用js ,直接修改hash值
showSelfAlert3(){
this.showMask3 = true;
this.$nextTick(() => {
window.location.hash = 'alert-dialog';
window.history.pushState( window.history.state || {}, document.title, location.href + '#alert-dialog');
});
}
原理:#代表网页中的一个位置。其右面的字符,就是该位置的标识符 为网页位置指定标识符,有两个方法。一是使用锚点,比如<a name="print"></a> ,二是使用id属性,比如<div id="print" > 单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页 pushState 与 location.hash = 的有个区别会导致问题:pushState的第三个参数url 并不会马上就加载,而是可能在后面,比如页面重载的时候执行,这是导致弹窗没有自动朗读的大问题所在吧 pushState()方法绝不会导致hashchange 事件被激活
使用focus实现
需要结合aria-live 属性
showSelfAlert(){
this.showMask3 = true;
this.$nextTick(() => {
document.getElementById('aria-mask-dialog').focus();
});
}
<div role="dialog" airia-live="polite" aria-label="xx对话框" tabindex="-1">弹窗内容</div>
aria-live属性,默认值为off。变化了之后不会自己读出来。polite是系统会在用户空闲的时候朗读,assertive是立马打断 实验下来是使用polite,屏幕阅读器也会当即就读出来
限制焦点
让弹窗底下的元素无法访问,使用aria-hidden属性
function switchCompAriaHidden(name, val) {
let comp = document.querySelector(name);
comp?.setAttribute?.('aria-hidden', val);
}
document.addEventListener("focus", function(event) {
var dialog = document.getElementById("my-dialog");
if (dialogOpen && !dialog.contains(event.target)) {
event.stopPropagation();
dialog.focus();
}
}, true);
使用事件捕获(event capturing)侦听focus事件 事件处理程序的阶段:捕获 - 目标 - 冒泡
focus:当focusable元素获得焦点时,不支持冒泡; focusin:和focus一样,只是此事件支持冒泡; blur:当focusable元素失去焦点时,不支持冒泡; focusout:和blur一样,只是此事件支持冒泡;
恢复焦点
关闭弹窗的时候,应该默认将焦点回退到弹窗显示之前相关的位置 实现:打开弹窗前记录最后一个焦点元素
let lastFocus = document.activeElement;
lastFocus.focus()
特殊情况根据场景实现: 非常不希望用户再次唤起这个弹窗; 这个弹窗操作完成之后,需要用户进入到下一个流程
多个浮层/弹窗的管理
历史的h5已经实现了一部分的pushState ,return popstateWatcher 在弹窗或浮层出现的时候,在浏览器记录里面push一下当前页面url。并对popstate 事件addEventListener,回退时关闭弹窗/浮层 目的是为了解决用户手势操作(左滑)回退浏览器,而这种默认操作可能与用户预期不一致。比如打开弹窗的时候,用户左滑关闭弹窗,结果关闭了整个页面回退到上一个页面
问题点
因为是事件监听,多个弹窗时多个监听,回退一下会关闭多个
优化
使用数组的unshift 和pop 实现堆栈管理,保证每次监听的事件时最新的
let historyWatcherArr = [];
function stateWatcherStep() {
if (historyWatcherArr && historyWatcherArr.length) {
let listennerNow = historyWatcherArr.pop(historyWatcherArr[0]);
window.removeEventListener('popstate', listennerNow);
}
historyWatcherArr.length && window.addEventListener('popstate', historyWatcherArr[0], false);
}
export default function pushHistory(callback, hashVal) {
let historyState = window.history.state || {};
historyState.url = location.href;
if (hashVal) {
historyState.hash = hashVal;
}
window.history.pushState(historyState, document.title, location.href + hashVal ? hashVal : '');
let popstateWatcher = function (e) {
popstateWatcher.clean();
if (typeof callback === 'function') {
callback();
callback = null;
}
};
popstateWatcher.remove = function () {
popstateWatcher.clean();
history.back();
};
popstateWatcher.clean = function () {
window.removeEventListener('popstate', popstateWatcher);
stateWatcherStep();
};
if (historyWatcherArr.length) {
window.removeEventListener('popstate', historyWatcherArr[0]);
}
window.addEventListener('popstate', popstateWatcher, false);
historyWatcherArr.unshift(popstateWatcher);
return popstateWatcher;
}
|