解决使残疾人可以使用模式窗口的问题

你好!



在本文中,我想告诉您如何在不使用“ aria-modal”属性的情况下实现可访问的模式



有点理论!



“区域模式”是用于告诉辅助技术(例如屏幕阅读器)当前对话框下的Web内容不可互操作(惰性)的属性。换句话说,模态下的任何元素都不能集中于单击,TAB / SHIFT + TAB导航或在传感器设备上滑动。



但是为什么我们不能在模态窗口中使用“ aria-modal”呢?



有以下几个原因:



  • 只是屏幕阅读器不支持
  • 被伪类“:之前/:之后”忽略


让我们继续执行。



实作



为了开始开发,我们需要选择可用模式窗口应具有的属性



  • 模态窗口之外的所有交互元素都必须被阻止以供用户操纵:单击,聚焦等...
  • 导航应仅通过浏览器的系统组件和模式本身的内容才可用(模式窗口之外的所有内容都应忽略)


空白



让我们使用一个模板,以免在创建模态窗口的分步说明上浪费时间。



HTML:



<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>
        <button type="button" id="infoBtn" class="btn"> Standart button </button>
        <button type="button" id="openBtn"> Open modal window</button>
        <div role="button" tabindex="0" id="infoBtn" class="btn"> Custom button </button>
    </div>
    <div>
        Lorem, ipsum dolor sit amet consectetur adipisicing elit. Deserunt maxime tenetur sint porro tempore aperiam! Eaque tempore repudiandae culpa omnis placeat, fugit nostrum quisquam in ipsa odit accusamus illum velit?
    </div>


    <div id="modalWindow" class="modal">
        <div>
            <button type="button" id="closeBtn" class="btn-close">Close</button>
            <h2>Modal window</h2>
            <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Expedita, doloribus.</p>
        </div>
    </div>
</body>
</html>


样式:



    .modal {
        position: fixed;
        font-family: Arial, Helvetica, sans-serif;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background: rgba(0,0,0,0.8);
        z-index: 99999;
        transition: opacity 400ms ease-in;
        display: none;
        pointer-events: none;
    }
    
    .active{
        display: block;
        pointer-events: auto;
    }

    .modal > div {
        width: 400px;
        position: relative;
        margin: 10% auto;
        padding: 5px 20px 13px 20px;
        border-radius: 10px;
        background: #fff;
    }

    .btn-close {
        padding: 5px;
        position: absolute;
        right: 10px;
        border: none;
        background: red;
        color: #fff;
        box-shadow: 0 0 10px rgba(0,0,0,0.5);
    }

    .btn {
        display: inline-block;
        border: 1px solid #222;
        padding: 3px 10px;
        background: #ddd;
        box-sizing: border-box;
    }


JS:



    let modaWindow = document.getElementById('modalWindow');

    document.getElementById('openBtn').addEventListener('click', function() {
        modaWindow.classList.add('active');
    });

    document.getElementById('closeBtn').addEventListener('click', function() {
        modaWindow.classList.remove('active');
    });


如果打开页面并尝试使用“ TAB / SHIFT + TAB”键导航到模式窗口后面的元素,则这些元素将获得焦点,如所附图片所示。



图片



要解决此问题,我们需要为所有交互元素的'tabindex'属性分配负值1。



1.为了进行进一步的工作,请创建具有以下属性和方法的类“ modalWindow”:



  • doc-页面文档。在其中我们建立一个模态窗口
  • modal-模态窗口的容器
  • InteractiveElementsList-交互式元素的数组
  • blockElementsList-页面块元素的数组
  • 构造函数-类的构造函数
  • create-用于创建模式窗口的方法
  • remove-用于删除模态的方法


2.让我们实现构造函数:



constructor(doc, modal) {
    this.doc = doc;
    this.modal = modal;
    this.interactiveElementsList = [];
    this.blockElementsList = [];
}


需要“ InteractiveElementsList”和“ blockElementsList”来包含在创建模态时已更改的页面元素。



3.创建一个常量,在该常量中我们将存储所有可以关注的元素的列表:



const INTERECTIVE_SELECTORS = ['a', 'button', 'input', 'textarea', '[tabindex]'];


4.在“创建”方法中,选择与我们的选择器匹配的所有元素,并设置所有“ tabindex = -1”(忽略已具有该值的元素)



 let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
 let element;
 for (let i = 0; i < elements.length; i++) {
     element = elements[i];
     if (!this.modal.contains(element)) {
         if (element.getAttribute('tabindex') !== '-1') {
               element.setAttribute('tabindex', '-1');
               this.interactiveElementsList.push(element);
         }
     }
 }


当我们使用特殊键或手势(在移动程序中)进行导航时,也会出现类似的问题,在这种情况下,我们不仅可以通过交互式元素进行导航,还可以通过文本进行导航。为了解决这个问题,我们需要添加



5。在这里,我们不需要创建一个数组来保存选择器,只需要抓取“ body”节点的所有子节点即可。



let children = this.doc.body.children;


6.第四步类似于步骤2,仅使用'aria-hidden'



for (let i = 0; i < children.length; i++) {
   element = children[i];
   if (!this.modal.contains(element)) {
      if (element.getAttribute('aria-hidden') !== 'true') {
          element.setAttribute('aria-hidden', 'true');
          this.blockElementsList.push(element);
       }
    }
}


完成“创建”方法:



create() {
    let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
    let element;
    for (let i = 0; i < elements.length; i++) {
        element = elements[i];
        if (!this.modal.contains(element)) {
            if (element.getAttribute('tabindex') !== '-1') {
                element.setAttribute('tabindex', '-1');
                this.interactiveElementsList.push(element);
            }
        }
    }

    let children = this.doc.body.children;
    for (let i = 0; i < children.length; i++) {
        element = children[i];
        if (!this.modal.contains(element)) {
            if (element.getAttribute('aria-hidden') !== 'true') {
                element.setAttribute('aria-hidden', 'true');
                this.blockElementsList.push(element);
            }
        }
    }
}


7.在第六步,我们实现相反的方法“ create”:



 remove() {
            let element;
            while(this.interactiveElementsList.length !== 0) {
                element = this.interactiveElementsList.pop();
                element.setAttribute('tabindex', '0');
            }

            while(this.interactiveElementsList.length !== 0) {
                element = this.interactiveElementsList.pop();
                element.setAttribute('aria-gidden', 'false');
            }
}


8.为此,我们需要创建“ modalWindow”类的实例,并调用“ create”和“ remove”方法:



    let modaWindow = document.getElementById('modalWindow');
    const modal = new modalWindow(document, modaWindow);

    document.getElementById('openBtn').addEventListener('click', function() {
        modaWindow.classList.add('active');
       // modal.create();
    });

    document.getElementById('closeBtn').addEventListener('click', function() {
        modaWindow.classList.remove('active');
       // modal.remove();
    });


完整的课程代码:



class modalWindow{
    constructor(doc, modal) {
        this.doc = doc;
        this.modal = modal;
        this.interactiveElementsList = [];
        this.blockElementsList = [];
    }

    create() {
        let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
        let element;
        for (let i = 0; i < elements.length; i++) {
            element = elements[i];
            if (!this.modal.contains(element)) {
                if (element.getAttribute('tabindex') !== '-1') {
                    element.setAttribute('tabindex', '-1');
                    this.interactiveElementsList.push(element);
                }
            }
        }

        let children = this.doc.body.children;
        for (let i = 0; i < children.length; i++) {
            element = children[i];
            if (!this.modal.contains(element)) {
                if (element.getAttribute('aria-hidden') !== 'true') {
                    element.setAttribute('aria-hidden', 'true');
                    this.blockElementsList.push(element);
                }
            }
        }
    }

    remove() {
        let element;
        while(this.interactiveElementsList.length !== 0) {
            element = this.interactiveElementsList.pop();
            element.setAttribute('tabindex', '0');
        }

        while(this.interactiveElementsList.length !== 0) {
            element = this.interactiveElementsList.pop();
            element.setAttribute('aria-gidden', 'false');
        }
    }


聚苯乙烯



如果在移动设备上无法解决文本元素导航的问题,则可以使用以下选择:



  const BLOCKS_SELECTORS = ['div', 'header', 'main', 'section', 'footer'];
  let children = this.doc.querySelectorAll(BLOCKS_SELECTORS .toString());


链接到有用的资源






All Articles