Nuxt 可访问性
Maschinelle Übersetzung ·1 % geprüft
让应用具备可访问性的绝大多数工作并不特定于 Nuxt:颜色对比度、表单语义和 ARIA 在这里与任何 Vue 或纯 HTML 应用中的工作方式相同,本指南末尾的实用资源也对这些内容进行了很好的介绍。
Nuxt 改变的是导航。当应用完成 hydration 后,它会在客户端进行路由切换,因此文档不会重新加载,浏览器也不再为你播报新页面或重置焦点。Nuxt 提供了一些功能来弥补其中一部分差距,另外一些约定则可以覆盖其余部分。本指南将介绍处理这些问题的最佳实践。
@nuxt/a11y 会在开发过程中,结合下面介绍的实践,帮助你发现组件中的可访问性问题。它目前处于 alpha 阶段,因此其 API 可能会发生变化。路由公告
屏幕阅读器会自行播报完整的页面加载,但它们无法得知客户端导航已经发生。<NuxtRouteAnnouncer> 通过渲染一个隐藏的实时区域,并在每次导航后将新页面的标题写入其中来解决这个问题:
<template>
<NuxtRouteAnnouncer />
<NuxtPage />
</template>公告器会读取由 Unhead 渲染的标题,因此它的效果取决于标题本身。如果两个路由共享相同的 <title>,用户在它们之间切换时将听不到任何内容。
当你需要播报其他内容,或更改播报的紧急程度时,请使用 useRouteAnnouncer:
<script setup lang="ts">
const { set } = useRouteAnnouncer()
const { data: results } = await useFetch('/api/search')
watch(results, (results) => {
set(`${results?.length ?? 0} results found`)
})
</script>对于非导航导致的页面内更新,例如表单验证或 toast,请改用 <NuxtAnnouncer> 和 useAnnouncer。
页面标题
由于路由播报器会读取文档标题,为每个路由设置独特的标题是你能做的最重要的事情。在 app.vue 中设置全局模板,并让每个页面填充自己的部分:
<script setup lang="ts">
useHead({
titleTemplate: title => title ? `${title} - Nuxt` : 'Nuxt',
})
</script><script setup lang="ts">
useHead({
title: 'About us',
})
</script>如果你的标题来自路由元数据,而不是页面本身,可以在布局中通过 useRoute 读取 definePageMeta 的值。
链接
使用 <NuxtLink> 进行应用内导航。它会渲染真正的 <a href="...">,这意味着它可以获得焦点、出现在 Tab 顺序中,并支持鼠标中键点击和“在新标签页中打开”。如果使用带有 @click 处理程序并调用 navigateTo 的 <div>,这些功能都需要你重新实现。
<template>
<NuxtLink to="/about">About page</NuxtLink>
</template>在菜单或面包屑导航中,与当前路由匹配的链接已经带有 aria-current="page",因此辅助技术可以识别你当前所在的位置。如果使用其他标记能更好地描述这种关系,例如多页表单中的某个步骤,请设置 ariaCurrentValue:
<template>
<NuxtLink
to="/checkout/payment"
aria-current-value="step"
>Payment</NuxtLink>
</template>Vue Router 根据路由及其参数判断链接是否匹配,不会考虑查询参数。因此,如果链接与当前页面的区别仅在查询参数,它也会被标记为当前页面:一组筛选项或 ?edit=<id> 列表中的多个行会同时被读作“当前页面”。一组元素中只能有一个当前项,因此这类链接应自行设置 aria-current。它的优先级高于路由器设置的值,而设为 undefined 会移除该属性:
<script setup lang="ts">
const route = useRoute()
</script>
<template>
<NuxtLink
:to="{ query: { status: 'open' } }"
:aria-current="route.query.status === 'open' ? 'true' : undefined"
>Open</NuxtLink>
</template>如果这些链接用于切换同一页面中的面板,那么使用 role="tab" 和 aria-selected 的 tabs 模式可能比 aria-current 更合适。
指向 public/ 目录中的文件或同源其他应用的链接,并不是 Vue Router 所识别的路由。请将它们标记为 external,以便浏览器执行真正的导航,而不是因为无法匹配路由而失败。
焦点管理
在客户端导航之后,焦点会保持在原来的位置,通常就是用户刚刚激活的链接。Vue Router 不会移动焦点,Nuxt 也不会,因此键盘用户可能需要再次遍历整个页头,才能到达刚刚发生变化的内容。
将跳过链接作为应用中的第一个 Tab 停止点是传统的解决方案,在初始页面加载时也同样有帮助:
<template>
<a
class="skip-link"
href="#main"
>跳转到主要内容</a>
<AppHeader />
<main id="main">
<NuxtPage />
</main>
</template>
<style>
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
position: static;
}
</style>跟随指向 #main 的链接会将浏览器顺序焦点导航的起点移至 <main>。虽然 <main> 本身不可聚焦,但在当前浏览器中,接下来的 Tab 会移至该起点之后的第一个可聚焦元素:如果 <main> 内含有可聚焦元素,就移至其中的第一个;否则移至 <main> 之后。
如果适合你的应用,你还可以更进一步,在导航改变路径时通过插件将焦点移动到主区域。只有在 <main> 设置了 tabindex="-1" 后,脚本才能将焦点移至其中。请使用 -1 而不是正值,因为正值会改变元素在 Tab 顺序中的位置,让其他人感到意外:
<template>
<!-- ... -->
<main
id="main"
tabindex="-1"
>
<NuxtPage />
</main>
</template>仅更改查询参数或 hash 的导航(例如筛选链接或跳过链接本身)会让焦点保持原位:
export default defineNuxtPlugin(() => {
useRouter().afterEach((to, from) => {
if (to.path === from.path) {
return
}
nextTick(() => document.getElementById('main')?.focus())
})
})tabindex="-1" 后,如果点击 <main> 内不属于其他可聚焦元素的内容,焦点可能会落到 <main> 本身,因为浏览器会聚焦被点击元素最近的可聚焦祖先。如果你的应用会将焦点返回到先前聚焦的元素(例如关闭对话框时),请考虑这一点。<main>,可以快速发现大多数焦点问题。滚动行为
Nuxt 会在进入新路由时滚动到顶部,在用户返回时恢复先前的位置,并滚动到哈希目标。如果你需要不同的行为,例如平滑滚动或不同的偏移量,请配置 scrollBehaviorType,或在 router.options.ts 中编写自己的 scrollBehavior。请注意,平滑滚动应遵循用户的 prefers-reduced-motion 设置。
实用资源
Was this helpful?