Browse Source

无障碍:首页键盘访问功能

任一存 3 years ago
parent
commit
de46027b77

+ 7 - 2
web/README.md

@@ -34,7 +34,7 @@ See [Configuration Reference](https://cli.vuejs.org/config/).
 * data-aria-navigation-area: 手动添加到导航区。
 * data-aria-viewport-area: 手动添加到视窗区。
 * data-aria-interaction-area: 手动添加到交互区。
-* tabindex: 手动添加,表示这个元素需要在被聚焦、鼠标hover时提取出无障碍信息来朗读、显示。
+* tabindex: 手动添加,表示这个元素需要能够被聚焦,且在被聚焦、鼠标hover时应该提取出其上储存的无障碍信息来朗读、显示。对于a、button、input等天生可以聚焦的标签,如果不写这个,则能聚焦但不会提取出无障碍信息。
 * aria-label: 手动添加,可选,表示元素在无障碍信息中显示的类型,不写的话会根据元素标签得到其在无障碍信息中显示的类型。
 * aria-description: 手动添加,可选,表示元素在无障碍信息中显示的详细描述,不写的话会拿元素的innerText作为描述。
 
@@ -61,4 +61,9 @@ See [Configuration Reference](https://cli.vuejs.org/config/).
 * audio-abort: 音频中止(并非暂停,还不知道什么情况下会触发这个)
 
 ### 监听原型对象上的$eventBus的消息
-* request-read: 请求朗读指定的文本
+* request-read: 请求朗读指定的文本
+
+## 使用无障碍组件时,路由跳转相关规则
+* 单纯的路由跳转,尽量用a标签或router-link标签,不要在点击事件回调里跳转,因为上述标签天生支持键盘操作。
+* 复杂逻辑的路由跳转,不得不在事件回调里进行操作时,除了响应click事件,还要响应keydown事件:`@keydown.enter.passive...`
+* 因为前端路由变化不会导致被复用的组件失焦,而很多原本只是hover到某个区域时显示的跳转链接,为了支持键盘访问,改为了在focus-within时也显示,又不希望跳转完成后这些跳转链接还显示着,所以要给前端路由对象添加全局后置钩子,在其中令focus的元素blur:`router.afterEach(() => {document.activeElement.blur()})`

+ 5 - 0
web/src/router/index.js

@@ -260,4 +260,9 @@ router.beforeEach((to, from, next) => {
   next()
 })
 
+// 因为前端路由变化不会导致被复用的组件失焦,而很多原本只是hover到某个区域时显示的跳转链接,为了支持键盘访问,改为了在focus-within时也显示,又不希望跳转完成后这些跳转链接还显示着,所以要给前端路由对象添加全局后置钩子,在其中令focus的元素blur
+router.afterEach(() => {
+  document.activeElement.blur()
+})
+
 export default router

+ 9 - 0
web/src/utils.js

@@ -25,6 +25,15 @@ function mapTags(tag) {
 
 function extractTextForMagnify(e) {
   let meaningfulNode = e.path[0]
+
+  // 如果天然能focus,但没有被加上tabindex属性,比如第三方组件内部可focus元素的情况
+  if (
+    ['A', 'AREA', 'BUTTON', 'INPUT', 'SELECT', 'IFRAME'].includes(meaningfulNode.tagName) &&
+    !meaningfulNode.getAttribute('tabindex')
+  ) {
+    return
+  }
+  
   while (!meaningfulNode.getAttribute || !meaningfulNode.getAttribute('tabindex')) {
     meaningfulNode = meaningfulNode.parentNode
     if (!meaningfulNode) {

+ 122 - 42
web/src/views/Home/index.vue

@@ -5,12 +5,53 @@
       data-aria-viewport-area tabindex="0"
       aria-label aria-description="You've reached the slider section. This section has four image URLs. To browse the content, please use the tab key."
     >
-      <el-carousel-item v-for="item in 4" :key="item">
-        <img
-          :src="require(`@/assets/images/swiper${item}.jpg`)"
-          alt=""
-          @click="skip(item)"
-        />
+      <el-carousel-item key="1">
+        <router-link to="/Layout/ExhibitionsInfo?id=1&k=1"
+          tabindex="0"
+          aria-label="Image link"
+          aria-description="The Great Journey: the 100th Anniversary of the Founding of the Communist Party of China."
+        >
+          <img
+            :src="require(`@/assets/images/swiper1.jpg`)"
+            alt="The Great Journey: the 100th Anniversary of the Founding of the Communist Party of China."
+          />
+        </router-link>
+      </el-carousel-item>
+      <el-carousel-item key="2">
+        <router-link to="/Layout/Collections/Bronzes"
+          tabindex="0"
+          aria-label="Image link"
+          aria-description="What's new? More arts just added to our online collections."
+        >
+          <img
+            :src="require(`@/assets/images/swiper2.jpg`)"
+            alt="What's new? More arts just added to our online collections."
+          />
+        </router-link>
+      </el-carousel-item>
+      <el-carousel-item key="3">
+        <router-link to="/Layout/ExhibitionsInfo?id=10&k=2"
+          tabindex="0"
+          aria-label="Image link"
+          aria-description="Folk Customs in old beijing."
+        >
+          <img
+            :src="require(`@/assets/images/swiper3.jpg`)"
+            alt="Folk Customs in old beijing."
+          />
+        </router-link>
+      </el-carousel-item>
+      <el-carousel-item key="4">
+        <router-link to="/Layout/Publications/2"
+          tabindex="0"
+          aria-label="Image link"
+          aria-description="Exhibition catalogues newly added to our website."
+        >
+          <img
+            :src="require(`@/assets/images/swiper4.jpg`)"
+            alt="Exhibition catalogues newly added to our website."
+          />
+        </router-link>
       </el-carousel-item>
     </el-carousel>
     <!-- 底部 -->
@@ -18,62 +59,95 @@
       class="bottomNav" data-aria-viewport-area tabindex="0"
       aria-label aria-description="You've reached the pop-up window section; this section contains four URLs; please use the tab key to go through the content."
     >
-      <div class="t1" @click="$router.push('/Layout/Visit/2')">
+      <div
+        class="t1" @click="$router.push('/Layout/Visit/2')"
+        tabindex="0"
+        aria-label="Link"
+        aria-description="Reservation"
+      >
         <div class="title">
           <span>Reservation</span>
         </div>
         <div class="info">
-          <div class="p">
-            <img src="../../assets/images/bott1.jpg" alt="" />
-          </div>
+          <router-link class="p" to="/Layout/Visit/2"
+            tabindex="0"
+            aria-label="Image link"
+            aria-description="Reservation"
+          >
+            <img src="../../assets/images/bott1.jpg" alt="Reservation"/>
+          </router-link>
           <div class="d">
-            <p class="n1">Online Reservation</p>
-            <p class="n2">Telephone Reservation</p>
-            <p class="n3">Individual <span>+86 (10) 63393339</span></p>
-            <p class="n3">Group <span>+86 (10) 63370458</span></p>
+            <p class="n1" tabindex="0">Online Reservation</p>
+            <p class="n2" tabindex="0">Telephone Reservation</p>
+            <p class="n3" tabindex="0">Individual <span>+86 (10) 63393339</span></p>
+            <p class="n3" tabindex="0">Group <span>+86 (10) 63370458</span></p>
           </div>
         </div>
       </div>
-      <div class="t2" @click="$router.push('/Layout/Visit/1')">
+      <div class="t2" @click="$router.push('/Layout/Visit/1')"
+        tabindex="0"
+        aria-label="Link"
+        aria-description="Visit Info"
+      >
         <div class="title">
           <span>Visit Info</span>
         </div>
         <div class="info">
-          <div class="p">
-            <img src="../../assets/images/bott2.jpg" alt="" />
-          </div>
+          <router-link class="p" to="/Layout/Visit/1"
+            tabindex="0"
+            aria-label="Image link"
+            aria-description="Hours, Direction & Admission"
+          >
+            <img src="../../assets/images/bott2.jpg" alt="Hours, Direction & Admission"/>
+          </router-link>
           <div class="d">
-            <p class="n4">Opening Hours 09:00-17:00</p>
-            <p class="n5">No admission after 16:00</p>
-            <p class="n5">Closed on Monday</p>
-            <p class="n6">Phone</p>
-            <p>+86 (10) 63370491</p>
+            <p class="n4" tabindex="0">Opening Hours 09:00-17:00</p>
+            <p class="n5" tabindex="0">No admission after 16:00</p>
+            <p class="n5" tabindex="0">Closed on Monday</p>
+            <p class="n6" tabindex="0">Phone</p>
+            <p tabindex="0">+86 (10) 63370491</p>
           </div>
         </div>
       </div>
-      <div class="t3" @click="botskip()">
+      <div class="t3" @click="botskip()"
+        tabindex="0"
+        aria-label="Link"
+        aria-description="Partners & Connections"
+      >
         <div class="title">
           <span>Partners & Connections</span>
         </div>
         <div class="info">
-          <div class="p">
-            <img src="../../assets/images/bott3.jpg" alt="" />
-          </div>
+          <router-link class="p" to="/Layout/About"
+            tabindex="0"
+            aria-label="Image link"
+            aria-description="Partners & Connections"
+          >
+            <img src="../../assets/images/bott3.jpg" alt="Partners & Connections"/>
+          </router-link>
           <div class="d">
-            <p class="n7">Partners & Connections</p>
+            <p class="n7" tabindex="0">Partners & Connections</p>
           </div>
         </div>
       </div>
-      <div class="t4" @click="$router.push('/Layout/Events')">
+      <div class="t4" @click="$router.push('/Layout/Events')"
+        tabindex="0"
+        aria-label="Link"
+        aria-description="Events"
+      >
         <div class="title">
           <span>Events</span>
         </div>
         <div class="info">
-          <div class="p">
-            <img src="../../assets/images/bott4.jpg" alt="" />
-          </div>
+          <router-link class="p" to="/Layout/Events"
+            tabindex="0"
+            aria-label="Image link"
+            aria-description="Events"
+          >
+            <img src="../../assets/images/bott4.jpg" alt="Events"/>
+          </router-link>
           <div class="d">
-            <p class="n8">Partners & Connections</p>
+            <p class="n8" tabindex="0">Events</p>
           </div>
         </div>
       </div>
@@ -95,14 +169,6 @@ export default {
   watch: {},
   //方法集合
   methods: {
-    skip(val) {
-      let temp = "";
-      if (val === 1) temp = "/Layout/ExhibitionsInfo?id=1&k=1";
-      else if (val === 2) temp = "/Layout/Collections/Bronzes";
-      else if (val === 3) temp = "/Layout/ExhibitionsInfo?id=10&k=2";
-      else if (val === 4) temp = "/Layout/Publications/2";
-      this.$router.push(temp);
-    },
     // 底部的跳转
     botskip() {
       this.$router.push("/Layout/About").catch(() => {});
@@ -147,9 +213,10 @@ export default {
     position: absolute;
     left: 0;
     bottom: 0;
-    z-index: 999;
+    z-index: 4;
     display: flex;
     & > div {
+      display: block;
       cursor: pointer;
       width: 25%;
       height: 58px;
@@ -181,6 +248,7 @@ export default {
           padding: 20px;
         }
         .p {
+          display: block;
           img {
             vertical-align: bottom;
           }
@@ -199,6 +267,18 @@ export default {
     .t4:hover .info {
       height: 270px;
     }
+    .t1:focus-within .info {
+      height: 320px;
+    }
+    .t2:focus-within .info {
+      height: 320px;
+    }
+    .t3:focus-within .info {
+      height: 270px;
+    }
+    .t4:focus-within .info {
+      height: 270px;
+    }
     .t1 {
       .title {
         span {

+ 3 - 3
web/src/views/accessibility.vue

@@ -21,7 +21,7 @@
     >
       <div class="text-wrapper">
         <p>
-          {{ elemType + ': ' + elemDisc }}
+          {{ elemType + (elemType ? ': ' : '') + elemDisc }}
         </p>
       </div>
       <button
@@ -672,7 +672,7 @@ export default {
       XHR.open("POST", "http://192.168.0.245:8008/api/tts/toMp3")
       XHR.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
       XHR.send(JSON.stringify({
-        content: text || this.elemType + ': ' + this.elemDisc
+        content: text || this.elemType + (this.elemType ? ': ' : '') + this.elemDisc
       }))
     }, 500),
     keyEventHandler(e) {
@@ -988,7 +988,7 @@ a {
   position: fixed;
   top: 0;
   width: 100%;
-  z-index: 999;
+  z-index: 5;
   .crosshair-h {
     position: fixed;
     width: 100%;

+ 70 - 38
web/src/views/layout/index.vue

@@ -4,40 +4,57 @@
     <div class="aria-control-target">
       <!-- 公共头部 -->
       <div class="topNav">
-        <div class="mainbav_wrap">
+        <div class="main_nav_wrap">
           <h1 class="logo">
             <img src="@/assets/images/logo.png" alt="" />
           </h1>
           <ul
-            class="mainbav" tabindex="0" data-aria-navigation-area
+            class="main_nav" tabindex="0" data-aria-navigation-area
             aria-label aria-description="You have reached the horizontal menu at the top of the website. There are eight dropdown menus and 34 options in this section. To browse the content, please use the tab key."
           >
             <li
               @click="skipOne(item.url)"
+              @keydown.enter.passive="skipOne(item.url)"
               :class="{ active: $route.meta.myName === item.name }"
               v-for="item in topData"
               :key="item.id"
+              tabindex="0"
+              aria-label="Link"
+              :aria-description="item.name"
             >
               <span>{{ item.name }}</span>
-              <ul class="mainbav_sub">
+              <ul class="main_nav_sub">
                 <li
                   :class="{ active2: menaInd === val.url }"
                   v-for="(val, index) in item.children"
                   :key="index"
                   @click.stop="skipTow(val.url, item.about)"
+                  @keydown.enter.passive.stop="skipTow(val.url, item.about)"
+                  tabindex="0"
+                  aria-label="Link"
+                  :aria-description="val.name"
                 >
-                  {{ val.name }}
+                  <span>{{ val.name}}</span>
                 </li>
               </ul>
             </li>
             <!-- 中文网 -->
             <li class="language">
-              <a href="http://www.capitalmuseum.org.cn/" target="_blank">中文</a>
+              <a href="http://www.capitalmuseum.org.cn/" target="_blank"
+                tabindex="0"
+                aria-description="中文网站"
+              >
+                中文
+              </a>
             </li>
             <!-- 爱心模式 -->
             <li class="love">
               <span>Caring Mode</span>&nbsp;
-              <el-switch v-model="loveFlag" active-color="#AB3434"> </el-switch>
+              <el-switch v-model="loveFlag" active-color="#AB3434"
+                tabindex="0"
+                aria-label="Button"
+                aria-description="Caring Mode"
+              />
             </li>
           </ul>
         </div>
@@ -52,6 +69,8 @@
             placeholder="search..."
             suffix-icon="el-icon-search"
             v-model="searchTxt"
+            tabindex="0"
+            aria-description="search"
           >
           </el-input>
           <div class="btnn" @click="search"></div>
@@ -65,14 +84,19 @@
         aria-label aria-description="You've reached footer section at the bottom of the website. This section contains five URLs. To browse the content, please use the tab key."
       >
         <div>
-          <span
-            @click="footTo(item.path)"
-            v-for="item in footerData"
-            :key="item.name"
-            v-html="item.name"
-          ></span>
+          <template v-for="item in footerData">
+            |
+            <router-link
+              class="router-link"
+              :to="item.path"
+              :key="item.name"
+              v-html="item.name"
+              tabindex="0"
+              :aria-describedat="item.name"
+            />
+          </template>
         </div>
-        <p>
+        <p tabindex="0">
           Capital Museum. China 16 Fuxingmenwai Street, Xicheng District, Beijing
           100045, P.R.China.
         </p>
@@ -84,13 +108,25 @@
       >
         <ul class="rightIco">
           <li title="game">
-            <img src="@/assets/images/game.png" alt="" />
+            <a
+              href=""
+              tabindex="0"
+              aria-description="shadow art game"
+            >
+              <img src="@/assets/images/game.png" alt="" />
+            </a>
           </li>
           <li>
-            <img src="@/assets/images/code.png" alt="" />
+            <img src="@/assets/images/code.png" alt="" tabindex="0"/>
             <div>
-              <img src="@/assets/images/index_ewm1.png" alt="" />
-              <img src="@/assets/images/index_ewm.jpg" alt="" />
+              <img src="@/assets/images/index_ewm1.png" alt="" 
+                tabindex="0"
+                aria-description=""
+              />
+              <img src="@/assets/images/index_ewm.jpg" alt=""
+                tabindex="0"
+                aria-description=""
+              />
             </div>
           </li>
         </ul>
@@ -115,10 +151,10 @@ export default {
       loveFlag: false,
       searchTxt: "",
       footerData: [
-        { name: "|&emsp; Site Index", path: "/Layout/Index" },
-        { name: "|&emsp; Terms of Use", path: "/Layout/Use" },
-        { name: "|&emsp; Events", path: "/Layout/Events" },
-        { name: "|&emsp; Employment", path: "/Layout/Employment" },
+        { name: "Site Index", path: "/Layout/Index" },
+        { name: "Terms of Use", path: "/Layout/Use" },
+        { name: "Events", path: "/Layout/Events" },
+        { name: "Employment", path: "/Layout/Employment" },
       ],
       // 控制二级菜单的高亮
       menaInd: null,
@@ -161,10 +197,6 @@ export default {
         params: {txt:this.searchTxt},
       });
     },
-    // 底部的跳转
-    footTo(path) {
-      this.$router.push(path).catch(() => {});
-    },
     // 第一级的跳转
     skipOne(url) {
       this.$router.push("/Layout/" + url).catch(() => {});
@@ -207,8 +239,8 @@ export default {
   position: absolute;
   top: 0;
   left: 0;
-  z-index: 990;
-  .mainbav_wrap {
+  z-index: 4;
+  .main_nav_wrap {
     width: 1300px;
     margin: 0 auto;
     & > h1 {
@@ -220,7 +252,7 @@ export default {
         vertical-align: top;
       }
     }
-    .mainbav {
+    .main_nav {
       font-size: 14px;
       color: #fff;
       line-height: 60px;
@@ -235,19 +267,18 @@ export default {
         display: inline;
         margin-right: 25px;
         position: relative;
-        &:hover {
+        &:hover, &:focus-within {
           & > span {
             border-bottom: 2px solid #ca000a;
           }
-          .mainbav_sub {
+          .main_nav_sub {
             display: block;
           }
         }
-        .mainbav_sub {
+        .main_nav_sub {
           display: none;
           position: absolute;
-
-          & > li {
+          span {
             border-left: 2px solid #660005;
             font-size: 14px;
             color: #fff;
@@ -350,7 +381,7 @@ export default {
           float: left;
         }
       }
-      &:hover {
+      &:hover, &:focus-within {
         background-color: #c20e11;
         & > div {
           display: block;
@@ -369,10 +400,11 @@ export default {
   color: #fff;
   line-height: 45px;
   background: url(../../assets/images/footer_bg.png) center top;
-  & > div {
-    & > span {
-      cursor: pointer;
-      margin-right: 15px;
+  div {
+    .router-link {
+      color: inherit;
+      margin-right: 10px;
+      margin-left: 10px;
     }
   }
 }