☰
盲人哥哥教会我:如何让购物搜索引擎对 VoiceOver 可用
2026/9/29 21:42:17 网站建设 项目流程



盲人哥哥教会我:如何让购物搜索引擎对 VoiceOver 可用

我的哥哥是盲人。当我让他用 iPhone 上的 VoiceOver 试一下我一直在开发的购物搜索引擎 OneFindMe 时,自动检查工具已经告诉我网站状况不错。可他尝试的第一件事——语音搜索——完全没有任何反应。这让我开始寻找所有检查工具看不见的问题。

以下是我做了哪些改变、哪里做错了,以及哪些部分比我想象的更有意思。

axe 只完成了大约三分之一的活儿

我和所有人一样从 axe-core 开始:对首页和结果页分别跑桌面端与移动端。它确实发现了真实问题:两个排序/筛选的<select>没有可访问名称,130 到 250 个灰色 7px 标签对比度不足,而且缺少main地标。全部修复后,扫描结果归零。

然后我按照哥哥的使用方式——只用键盘和屏幕阅读器——重新过了一遍网站,出现了一份完全不同的清单,而这些东西 axe 全都看不见:

  • 对屏幕阅读器来说根本不存在的弹窗。收藏、价格提醒和"相似产品"都是普通的<div>。它们在屏幕上打开,焦点却停留在后面,VoiceOver 一言不发。
  • 悄无声息的结果。视力正常的用户能看到网格逐渐填满,而盲人用户根本不知道搜索已经结束了。
  • 最先读到的是错误的东西。"热门"栏在 DOM 中位于结果上方,所以屏幕阅读器从顶部开始读时,会先遇到一排无关的畅销品,然后才是你搜索的内容。
  • 死按钮。☰ 菜单按钮、底栏的"主页"和"历史":可见、可聚焦,却什么都没接。对视力正常用户这只是小麻烦,对屏幕阅读器用户来说这是陷阱。
  • 表情符号被大声读出来。"火焰。每日优惠。""铃铛。价格提醒。"每一个都读。
  • 影响所有人的暗色模式 bug。一个白色"加载更多"按钮配浅色文字(对比度 1.2:1),还有一个帮助卡片有同样的问题。我此前只在结果页看过暗色模式。

每个产品只念一句话

一个产品卡片是包裹着图片、徽章、两个价格、标题、评分和订单数的链接。按 DOM 顺序读出来就是:"精选、降价百分之六十六、15.04、30.70、婴儿杯……"。我把它替换成链接上的一个aria-label:

const a11yLabel = [ shownTitle, `${sym}${p.price}` + (p.discount > 0 ? ` (${t.was} ${sym}${p.original_price})` : ""), p.rating ? `${t.rating} ${p.rating.toFixed(1)} ${t.of5}` : "", orders > 0 ? `${orders.toLocaleString()} ${ordersLabel}` : "", p.free_shipping ? t.freeShip : "" ].filter(Boolean).join(", ");

"保存"和"分享"按钮现在会说明它们属于哪个产品。一排 40 个一模一样的"保存"按钮毫无用处。

播报搜索结果

一个礼貌的 live region,外加一个我花了好一阵子才发现的细节:屏幕阅读器只在文本发生变化时才朗读 live region。连续两次完全相同的搜索会静默无声。先清空 region、在下一帧再填入内容就能解决:

function announce(msg) { el.textContent = ""; setTimeout(() => { el.textContent = msg; }, 60); }

搜索结束后,我还会用focus({ preventScroll: true })把焦点移到结果标题上。阅读位置跳到结果处,而视力正常的访客看不出任何移动。当焦点在筛选栏内时我会跳过这一步,因为用方向键浏览<select>时每走一步都会触发change事件。

"描述这个产品"

这是我最引以为傲的部分,也是我最初最担心做错的部分。

在 AliExpress 上,标题是一堆关键词的堆砌("2026 夏季男士老爹鞋 透气……")。真正的信息在照片里,而盲人购物者看不见照片。所以现在每条结果都有一个按钮——视觉上隐藏,但键盘和屏幕阅读器都能触达——把产品照片发送给视觉模型。模型会用页面的语言描述照片里实际显示的内容。

几点说明:

  • 模型选择比我预想的更重要。我在同一批希伯来语照片上对比了 Haiku、Sonnet 和 Opus。Haiku 会凭空编造照片里没有的部分。对一个无法核对照片的人来说,一个自信的错误描述比没有描述更糟。Sonnet 与 Opus 一样准确,而且大约快两秒——当有人在等待时,这很重要。
  • 提示词写明了不该做什么。只描述可见的内容。绝不编造尺寸、品牌或规格。如果不确定某个部件是什么,就略过它。
  • 按产品和语言做缓存。每个产品只付费一次,约 0.6 美分。第二次请求是即时的。
  • 对图片域名做白名单。接口只接受 AliExpress 的图片 URL。否则这就是一个挂在我密钥上的免费视觉 API。第一版拒绝了所有真实结果,因为结果卡片是通过我自己的/img?url=代理加载图片的。现在它会先解开这层代理。

语音搜索与 VoiceOver

两个意外发现:

为什么我自己做了个菜单,而不是安装第三方插件

无障碍覆盖层(带轮椅图标的那个小组件)很流行,但它们并不能让网站变得无障碍。许多盲人用户其实很反感它们,因为它们会和屏幕阅读器打架。我哥哥的 VoiceOver 全程不需要任何额外插件。

对低视力访客真正有用的是一个小而朴素的菜单:三种字号、高对比度、停止动画、高亮链接、易读字体,以及语音搜索的语言选择器。我自己做了一个,并且让它完全不挡屏幕阅读器的路。

这个过程中有两个坑:

目前的状态

它还在测试阶段,一些产品标题仍然以英文显示,因为卖家就是这么写的。这正是"描述"按钮存在的意义。

相关阅读(本站推荐):如何在 Windows 11/10 中关闭屏幕键盘(4 种方法) 如何在 Windows 11/10 中启用或禁用睡眠模式 如何在 Windows 11/10 中打开控制面板(5 种方法)

如果你也觉得无障碍和用户体验很重要,欢迎参考本站的 IT 教程文章,一起把网站做得更好用。

  1. 不要播报"正在聆听"。我最初的想法是让页面说"正在聆听,请说话"。但 VoiceOver 会在麦克风打开时把这句话大声读出来,而识别器会很乐意把它当作你的搜索内容转录下来。所以页面在聆听时保持沉默,只在出错时说话。
  2. iOS 会静默失败。当语音识别不被允许时(比如关闭了听写),Safari 的识别器会抛错,页面就沉默了。现在每条失败路径都会说明发生了什么,并提供键盘自带的听写麦克风——它在所有浏览器里都能用。
    • 网站布局用的是 px,所以改font-size毫无效果。而body.style.zoom能做到浏览器缩放的效果:放大并重新排版,在 390px 屏幕上也没有横向滚动。
    • 有一个页面 monkey-patch 了document.getElementById。对于不存在的 id 它会返回一个为真的 no-op 桩。于是每个"这个元素是否存在?"的检查都回答"是",菜单、live region 和样式表就永远不会被创建。那个页面现在改用基于querySelector的查找。
    • axe:首页在希伯来语、英语和阿拉伯语(浅色与暗色)下零违规,结果页在希伯来语和英语(桌面端与移动端)下同样零违规。
    • 键盘:跳转链接、处处可见的焦点、能锁定焦点并在 Escape 时归还焦点的对话框、对话框打开时背景区域inert。
    • 真实使用:我哥哥用手机上的 VoiceOver 实际操作。他的结论是:一切都像应有的那样无障碍了。他还在继续使用,并且不断发来使用笔记。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询