从键盘和屏幕阅读器正确隐藏内容 [英] Properly hiding content from both keyboard and screen reader
问题描述
我们在页面上有一个模式,当隐藏时,我们希望不让键盘用户能够进入内容,也不允许屏幕阅读器阅读.
为了处理这个问题,在父 DIV 上,我进行了设置,以便在隐藏时,它具有以下内容:
[子 HTML/内容]<div>不幸的是,这不起作用.您仍然可以使用 Tab 键进入内容并阅读内容(至少通过 Chrome 和使用 VoiceOver).
理想情况下,我们还可以设置 display: none
——我可能可以这样做——但目前我们依赖于一些 CSS 过渡动画,因此需要设置它动画后以编程方式.
然而,在走这条路之前,我最初的理解是 aria-hidden 和 tabindex 应该解决这个问题吗?
解决方案 简短回答
使用 display:none
没有过渡将是最好的选择,并且不需要 aria-hidden
.
如果您需要进行过渡,请先进行过渡,然后在过渡后设置 display: none
属性.
注意失去焦点,但如果过渡超过 100 毫秒,则必须进行大量焦点管理以解决设置 display:none
的延迟.
更长的答案
aria-hidden="true"
从可访问性树中删除项目及其子项.但是,它不会阻止可以接收焦点(即
)的子节点接收焦点.
tabindex=-1"
不会从已经可聚焦的子元素中移除焦点.
解决所有问题的最简单方法是移除过渡并简单地切换显示属性.这不仅解决了你的注意力问题,而且还消除了对 aria-hidden
的需求,让事情变得更简单.
话虽如此,转换可能是您规范的一部分并且不可避免.如果是这种情况,则需要考虑一些事项.
在我们在评论和您的问题中的讨论中,您提到使用 setTimeout
在转换完成后将 display 属性设置为 none.
这种方法存在问题,具体取决于您的设计.
如果下一个制表位在被隐藏的区域内,那么在转换期间有人可能导航到即将被隐藏的区域内的元素是可行的.
如果发生这种情况,页面上的焦点就会丢失.根据浏览器的不同,这可能会导致焦点返回到页面顶部.这是非常令人沮丧的事情,并且也可能在 WCAG 原则中的逻辑选项卡顺序/稳健性下构成失败.
在隐藏上实现动画的最佳方法是什么?
由于焦点问题,我建议使用以下过程来隐藏带有过渡的内容:-
- 第二个导致区域被隐藏的按钮/代码被激活(预淡出)在
中的所有交互元素上设置 tabindex=-1"
要隐藏的 div>
(或者如果它们是输入,则设置 disabled
属性). - 通过您使用的任何方式开始转换(即,将类添加到将触发转换的项目中).
- 转换完成后,在项目上设置
display: none
. - 如果您想让
再次可见,请执行完全相反的操作.通过这样做,您可以确保没有人会意外进入 div 并失去焦点.这对依赖键盘进行导航的每个人都有帮助,而不仅仅是屏幕阅读器用户.
如何实现这一点的一个非常粗略的例子如下.它可以根据容器的 ID 重用,因此希望能给你一个好的起点来写一些更健壮的东西(而且不那么丑!嘿嘿)
我已添加注释以尽我所能进行解释.我已将过渡设置为 2 秒,以便您可以检查并查看事物的顺序.
最后,我添加了一些 CSS 和 JS 来解释那些表示由于运动敏感性而更喜欢减少运动的人.在这种情况下,动画时间设置为 0.
隐藏项目以管理 tabindex 和恢复 tabindex 如果再次可见的粗略示例.
var content = document.getElementById('contentDiv');var btn = document.getElementById('btn_toggle');var animationDelay = 2000;//我们应该考虑前庭运动障碍等的人,如果他们表示他们更喜欢减少运动.我们将动画时间设置为 0 秒.var motionQuery = matchMedia('(prefers-reduced-motion)');函数 handleReduceMotionChanged() {如果(motionQuery.matches){动画延迟 = 0;} 别的 {动画延迟 = 2000;}}motionQuery.addListener(handleReduceMotionChanged);handleReduceMotionChanged();//用于将具有给定 ID 的父级的所有子级的 tabindex 设置为 -1 的主要功能(并反转该过程)函数 hideOrShowAllInteractiveItems(parentDivID){//所有可聚焦元素的选择器列表.var focusableItems = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', '[tabindex]:not([disabled])', '[contenteditable=true]:not([disabled])'];//构建一个查询字符串,它针对父 div ID 和我们的可聚焦项列表中的所有子元素.var queryString = "";for (i = 0, leni = focusableItems.length; i < leni; i++) {queryString += "#" + parentDivID + " " + focusableItems[i] + ", ";}queryString = queryString.replace(/,\s*$/, "");var focusableElements = document.querySelectorAll(queryString);for (j = 0, lenj = focusableElements.length; j
@keyframes in {0% { 变换:缩放(0);不透明度:0;可见性:隐藏;}100% { 变换:缩放(1);不透明度:1;可见性:可见;}}@关键帧出{0% { 变换:缩放(1);不透明度:1;可见性:可见;}100% { 变换:缩放(0);不透明度:0;可见性:隐藏;}}#contentDiv {背景:灰色;白颜色;填充:16px;底边距:10px;}#contentDiv.show {动画:在 2 秒内缓解两者;}#contentDiv.hide {动画:出 2 秒缓和;}/*****我们应该考虑患有前庭运动障碍等的人,如果他们表示他们更喜欢减少运动.***/@media(喜欢减少运动){#contentDiv.show,#contentDiv.hide{动画:无;}}
<p>一些需要隐藏的信息</p><输入/><按钮>一个按钮</按钮><button tabindex="1">一个需要恢复的带有正tabindex的按钮</button><button id="btn_toggle">隐藏 Div
We have a modal on a page that, when hidden, we want to not have keyboard users be able to tab into the content, nor have screen readers read.
To handle that, on the parent DIV, I've set up so that, when hidden, it has the following:
<div aria-hidden="true" tabindex="-1">
[child HTML/content]
<div>
Unfortunately, this isn't working. You can still tab into the content and the content is read (at least via Chrome and using VoiceOver).
Ideall, we'd also set display: none
-- which I may be able to do -- but at the moment we're dependant on some CSS transition animations so would need to set that programmatically after animation.
Prior to going that route, however, is there anything I am missing from my initial understanding that aria-hidden and the tabindex should be taking care of the issue?
解决方案 Short Answer
Using display:none
without a transition would be the best option and would negate the need for aria-hidden
.
If you are required to have a transition then do the transition and then set the display: none
property after the transition.
Be careful for lost focus though if you transition is over 100ms, you have to do a lot of focus management to account for the delay in setting display:none
.
Longer Answer
aria-hidden="true"
removes an item and its children from the accessibility tree. However it does not stop children that can receive focus (i.e. an <input>
) from receiving focus.
tabindex="-1"
will not remove focus from children elements that are already focusable.
The simplest way to solve all problems would be to remove the transition and simply toggle the display property. This not only solves your focus problem but it also removes the need for aria-hidden
, making things simpler.
With that being said a transition may be part of your spec and unavoidable. If that is the case there are a few things to consider.
In our discussions in the comments and within your question you mentioned using setTimeout
to set the display property to none after the transition has completed.
There is an issue with this approach depending on your design.
If the next tab stop is within the area that is being hidden it is feasible that during the transition someone may navigate to an element within the area that is about to be hidden.
If this were to happen focus on the page would be lost. Depending on the browser this could result in focus returning to the top of the page. This is something that would be highly frustrating and also probably constitute a fail under logical tab order / robustness in WCAG principles.
What would be the best way to achieve an animation on hide?
Because of the focus issue I would recommend the following process for hiding the content with a transition:-
- The second the button / code that causes the area to be hidden is activated (pre fade-out) set
tabindex="-1"
on all interactive elements within the <div>
that is to be hidden (or if they are inputs set the disabled
attribute).
- Start the transition by whatever means you are using (i.e. add the class to the item that will trigger the transition).
- After the transition is complete set the
display: none
on the item.
- Do the exact opposite if you wish to make the
<div>
visible again.
By doing this you ensure that nobody can accidentally tab into the div and lose focus. This helps everyone who relies on keyboard for navigation, not just screen reader users.
A very rough example of how to achieve this is below. It can be reused based on an ID of a container so hopefully will give you a good starting place to write something a little more robust (and less ugly! hehe)
I have added comments to explain as best I can. I have set the transition to 2 seconds so you can inspect and see the order of things.
Finally I have included some CSS and JS to account for people who have indicated they prefer reduced motion due to motion sensitivity. In this case the animation time is set to 0.
Rough example accounting for hiding items to manage tabindex and restoring tabindex if made visible again.
var content = document.getElementById('contentDiv');
var btn = document.getElementById('btn_toggle');
var animationDelay = 2000;
//We should account for people with vestibular motion disorders etc. if they have indicated they prefer reduced motion. We set the animation time to 0 seconds.
var motionQuery = matchMedia('(prefers-reduced-motion)');
function handleReduceMotionChanged() {
if (motionQuery.matches) {
animationDelay = 0;
} else {
animationDelay = 2000;
}
}
motionQuery.addListener(handleReduceMotionChanged);
handleReduceMotionChanged();
//the main function for setting the tabindex to -1 for all children of a parent with given ID (and reversing the process)
function hideOrShowAllInteractiveItems(parentDivID){
//a list of selectors for all focusable elements.
var focusableItems = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', '[tabindex]:not([disabled])', '[contenteditable=true]:not([disabled])'];
//build a query string that targets the parent div ID and all children elements that are in our focusable items list.
var queryString = "";
for (i = 0, leni = focusableItems.length; i < leni; i++) {
queryString += "#" + parentDivID + " " + focusableItems[i] + ", ";
}
queryString = queryString.replace(/,\s*$/, "");
var focusableElements = document.querySelectorAll(queryString);
for (j = 0, lenj = focusableElements.length; j < lenj; j++) {
var el = focusableElements[j];
if(!el.hasAttribute('data-modified')){ // we use the 'data-modified' attribute to track all items that we have applied a tabindex to (as we can't use tabindex itself).
// we haven't modified this element so we grab the tabindex if it has one and store it for use later when we want to restore.
if(el.hasAttribute('tabindex')){
el.setAttribute('data-oldTabIndex', el.getAttribute('tabindex'));
}
el.setAttribute('data-modified', true);
el.setAttribute('tabindex', '-1'); // add `tabindex="-1"` to all items to remove them from the focus order.
}else{
//we have modified this item so we want to revert it back to the original state it was in.
el.removeAttribute('tabindex');
if(el.hasAttribute('data-oldtabindex')){
el.setAttribute('tabindex', el.getAttribute('data-oldtabindex'));
el.removeAttribute('data-oldtabindex');
}
el.removeAttribute('data-modified');
}
}
}
btn.addEventListener('click', function(){
contentDiv.className = contentDiv.className !== 'show' ? 'show' : 'hide';
if (contentDiv.className === 'show') {
content.setAttribute('aria-hidden', false);
setTimeout(function(){
contentDiv.style.display = 'block';
hideOrShowAllInteractiveItems('contentDiv');
},0);
}
if (contentDiv.className === 'hide') {
content.setAttribute('aria-hidden', true);
hideOrShowAllInteractiveItems('contentDiv');
setTimeout(function(){
contentDiv.style.display = 'none';
},animationDelay); //using the animation delay set based on the users preferences.
}
});
@keyframes in {
0% { transform: scale(0); opacity: 0; visibility: hidden; }
100% { transform: scale(1); opacity: 1; visibility: visible; }
}
@keyframes out {
0% { transform: scale(1); opacity: 1; visibility: visible; }
100% { transform: scale(0); opacity: 0; visibility: hidden; }
}
#contentDiv {
background: grey;
color: white;
padding: 16px;
margin-bottom: 10px;
}
#contentDiv.show {
animation: in 2s ease both;
}
#contentDiv.hide {
animation: out 2s ease both;
}
/*****We should account for people with vestibular motion disorders etc. if they have indicated they prefer reduced motion. ***/
@media (prefers-reduced-motion) {
#contentDiv.show,
#contentDiv.hide{
animation: none;
}
}
<div id="contentDiv" class="show">
<p>Some information to be hidden</p>
<input />
<button>a button</button>
<button tabindex="1">a button with a positive tabindex that needs restoring</button>
</div>
<button id="btn_toggle"> Hide Div </button>
这篇关于从键盘和屏幕阅读器正确隐藏内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文
相关文章
- 从屏幕阅读器隐藏图标的适当方法;
- 屏幕阅读器和 Javascript;
- 桌子和屏幕阅读器;
- 屏幕阅读器无法读取WPF TextBlock内容;
- 屏幕阅读器未正确读取价格(“$47.49");
- 让屏幕阅读器阅读使用JavaScript添加的新内容;
- 让屏幕阅读器阅读使用 JavaScript 添加的新内容;
- 电话号码和屏幕阅读器;
- MFC功能区和屏幕阅读器?;
- Java Swing JEditorPane和屏幕阅读器;
- 电话号码和屏幕阅读器;
- Javascript keyevent和JAWS屏幕阅读器;
- 屏幕阅读器没有正确读取价格(“47.49美元”);
- 屏幕阅读器在哪里?;
- 隐藏文本只适用于屏幕阅读器;
- 如何让屏幕阅读器响应在动态Web应用程序中显示和隐藏内容?;
- 如何让屏幕阅读器响应动态 Web 应用程序中的显示和隐藏内容?;
- 如何在没有HTML标记的情况下从屏幕阅读器隐藏CSS生成的内容?;
- 如何隐藏文本并使其可由屏幕阅读器访问?;
- 向屏幕阅读器隐藏图标的适当方式是什么;
- wx.html2.WebView 和屏幕阅读器;
- 如何在没有 HTML 标记的情况下从屏幕阅读器中隐藏 CSS 生成的内容?;
- 如何隐藏文本并使其可通过屏幕阅读器访问?;
- 如何隐藏文本并使屏幕阅读器可以访问它?;
- 键盘和条形码阅读器之间的区别;
其他开发最新文章
- 拒绝显示一个框架,因为它将'X-Frame-Options'设置为'sameorigin';
- 什么是&QUOT; AW&QUOT;在部分标志属性是什么意思?;
- 在运行npm install命令时获取'npm WARN弃用'警告;
- cmake无法找到openssl;
- 从Spark的scala中的* .tar.gz压缩文件中读取HDF5文件;
- Twitter :: Error :: Forbidden - 无法验证您的凭据;
- 我什么时候需要一个fb:app_id或者fb:admins?;
- 将.db文件导入R;
- npm通知创建一个lockfile作为package-lock.json。你应该提交这个文件;
- 拒绝执行内联脚本,因为它违反了以下内容安全策略指令:“script-src'self'”;