我试图让vue组件在我的网站上发生不同事件时动态地向屏幕阅读器宣布信息.

我有它的工作,点击一个按钮将填充一个跨度是aria-live="assertive"role="alert"的文本.但是,在第一次使用时,单击具有类似行为的其他按钮会导致NVDA在阅读新文本之前先阅读前一文本两次.这似乎发生在vue中,但在使用jquery的类似设置中不会发生,所以我猜这与vue呈现到DOM的方式有关.

我希望有一些方法可以解决这个问题,或者有一个更好的方法来向用户阅读文本,而不会有这个问题.非常感谢您的帮助.

Here is a simple component我在一个工作的代码Sandbox 中设置,以显示我遇到的问题(导航到components/HelloWorld.vue查看代码)——Note: This sandbox has changed per the answer below.组件的完整代码如下:

export default {
  name: "HelloWorld",
  data() {
    return {
      ariaText: ""
    };
  },
  methods: {
    button1() {
      this.ariaText = "This is a bunch of cool text to read to screen readers.";
    },
    button2() {
      this.ariaText = "This is more cool text to read to screen readers.";
    },
    button3() {
      this.ariaText = "This text is not cool.";
    }
  }
};
<template>
  <div>
    <button @click="button1">1</button>
    <button @click="button2">2</button>
    <button @click="button3">3</button><br/>
    <span role="alert" aria-live="assertive">{{ariaText}}</span>
  </div>
</template>

推荐答案

好的,所以我发现更一致的方法是,用新文本替换元素中的文本,用要读取的新文本向父容器添加新元素.我不是将文本存储为单个字符串,而是将其存储在一个字符串数组中,该数组将v-for个字符串存储在aria-live容器中的页面上.

我已经构建了一个完整的组件,它将以各种方式实现这一点,作为任何希望实现这一点的人的示例:

export default {
    props: {
        value: String,
        ariaLive: {
            type: String,
            default: "assertive",
            validator: value => {
                return ['assertive', 'polite', 'off'].indexOf(value) !== -1;
            }
        }
    },
    data() {
        return {
            textToRead: []
        }
    },
    methods: {
        say(text) {
            if(text) {
                this.textToRead.push(text);
            }
        }
    },
    mounted() {
        this.say(this.value);
    },
    watch: {
        value(val) {
            this.say(val);
        }
    }
}
.assistive-text {
    position: absolute;
    margin: -1px;
    border: 0;
    padding: 0;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
}
<template>
    <div class="assistive-text" :aria-live="ariaLive" aria-relevant="additions">
        <slot></slot>
        <div v-for="(text, index) in textToRead" :key="index">{{text}}</div>
    </div>
</template>

这可以通过将父容器上的变量设置为组件的v-model来使用,对该变量的任何更改都将被读取到屏幕阅读器一次(以及父容器成为选项卡焦点的任何时候).

它也可以由this.$refs.component.say(textToSay);触发——注意,如果父容器成为选项卡焦点,它也会再次触发.通过将元素放在不会接收焦点的容器中,可以避免这种行为.

它还包括一个插槽,这样可以像这样添加文本:<assistive-text>Text to speak</assistive-text>然而,这不应该是一个动态/胡须变量,否则当文本更改时,您将在原始问题中遇到问题.

我还用这个组件的一个工作示例更新了the sandbox posted in the question.

Vue.js相关问答推荐

虚拟DOM在Vue中姗姗来迟

如何在vue中动态加载组件

Vuex:如何等待动作完成?

使用上传组件而不触发 POST 请求

vuetifyjs:仅添加使用过的图标来构建

是否可以在没有 Vue 的情况下使用 Vuex?

VueJS 将 HTML 打印到页面

Vuetify v-select 组件宽度变化

在 v-for 的情况下如何从 v-model 输入更新 Vuex 存储

如何使用 laravel-mix 将 bootstrap-vue 加载到 Laravel 5 中?

VS代码中的红点是什么意思

为什么 Vue.js 使用 VDOM?

在路由更改之前显示确认对话框

如何通过 Axios 发送文件到 Laravel

如何设置 :id 前缀字符串?

如何使用 Vuetify 验证复选框组

TypeError: Object(...) 不是 Vue 中的函数

为什么 router.currentRoute.path 没有react?

vuejs 复制数据对象和删除属性也会从原始对象中删除属性

vue-cli@3 没有可用于依赖类型的模块工厂:CssDependency