function FeedItem() { const feedItemElement = useRef(null) return ( <ShortcutContext value={feedItemElement}> <div className="FeedItem" ref={feedItemElement}> <AnswerItem /> </div> </ShortcutContext> )}
在 FeedItem 中,以自身的 HTML element 来初始化一个新的快捷键实例并设置在 context 中,实际的 keydown 等键盘事件是注册在这个 element 上。所以子组件都可以通过 context 在这个实例上注册新的快捷键。function VoteButton() { const handleVote = () => console.log('voting') // 在 useShortcut 中查找当前 context 中存在的快捷键实例,并注册在实例上 useShortcut('v', handleVote) return <button onClick={handleVote}>赞同</button>}
在 VoteButton 中,就可以像响应点击事件一样注册并响应快捷键事件,看起来和写起来都非常清爽。列表导航的快捷键使用 J、K 进行 Vim 风格的列表导航是许多网站常见的快捷键设计[2]。下面是一些如果从零开始实现时,可能会遇到的细节问题:直接使用 element.focus() 方法会由浏览器决定一个合适的滚动位置,这通常是不符合预期的(对于知乎来说,是因为顶部有一个固定的导航栏,原生滚动经常会被这个导航栏挡住)。我们使用 preventScroll: true 参数禁用了这个原生的滚动,并使用自己计算的结果来 window.scrollTo() 到指定位置。不是所有元素都是可以被 focus 的。如果只是普通的 <div> 元素的话,可以设置 tabIndex 为 0 或 -1 使其可被 focus。如果设置为 -1 的话则只可以被 focus,但是不能被 Tab 键选中[3]。是否希望被 Tab 选中,主要在于该元素内是否有可以被阅读的元素,或者该元素本身有可交互的行为。如果在 focus 到某个列表元素后,又用滚轮或者触摸板移动了网页到很远的距离,再按 J、K 进行导航时就需要放弃已经 focus 的元素的,尽可能地 focus 在视图区域内可见的元素上[4],或者采用别的策略[5]。和其他类似产品不同,知乎大部分列表元素不是整个区域都可以点击并进入详情页,而是鼓励在当前页面展开并消费内容的(否则可以直接使用一个 <a> 标签包裹。这次特意实现了 focus 在列表项时,按 Enter 键可以进入详情页的快捷键。如果因为点击而 focus 在某个列表项时(比如第 2 项),按 J、K 导航时最好只是先在该元素上显示 focus outline,而不是直接进行快捷键导航(到第 1 项或者第 3 项)。因为此时用户不一定知道自己已经 focus 在这个列表项上。这和知乎不为点击行为导致的 focus 添加 outline 有关。Focus Outline 该在什么时候展现当使用快捷键进行浏览时,「知道光标在哪里」很重要。屏幕阅读器可以阅读当前 focus 元素的内容。如果不通过声音的话、就只能通过视觉样式来知道当前正在 focus 的元素是什么。Chrome 为 Input 标签默认添加的 focus outline浏览器默认会为可 focus 的元素通过 :focus 伪类增加一个 outline 样式,因为这个样式不是很好看,再加上可 focus 的元素往往也会单独设计一些响应点击的样式,所以一般产品或设计会要求工程师取消掉该样式。但是如果简单取消所有 focus 样式后,用鼠标当然知道「我在点哪里」,但在使用键盘访问的场合就根本不知道「我在哪里」了。最优雅的方案是使用 CSSWG 的 :focus-visible 伪类来添加 focus 样式(同时禁用原先的 :focus 样式),在 WICG 对该样式的 polyfill 中有详细的介绍。简单来说,就是「只有用键盘触发的 focus 才应该添加 focus 样式」。知乎按钮的 focus 样式知乎通过和该 polyfill 类似的思想实现了这一设计:在使用键盘操作后,会为 <html> 元素添加 data-focus-visible 属性。只有在包含该属性的情况下,各个元素才会添加 focus 样式。而且知乎还修改了默认的样式、更为美观。你可以使用 Tab 键 tab 到赞同按钮上查看这个效果。在开发这一部分功能的时候,还有一个特殊的设计:使用 ⌘+C 等快捷键进行复制粘贴操作(准确地说,是按键中包括 Control 或 Shift 等 Modifier Key)时,不认为是一般的键盘操作,也不展现 focus outline,否则 outline 会过于频繁地时有时无。有意思的是,Twitter 其实也已经做了类似的处理,让人感到大洋两岸的工程师都在为用户体验而努力…快捷键和可访问性的关系很多关于快捷键的讨论都会有视障用户的参与,因为使用屏幕阅读器浏览网页、必须使用包括 Tab 键在内的各种键盘快捷键进行光标定位与操作,他们是「最会用键盘刷知乎」的人。但是,对视障用户的支持远不止添加快捷键这么简单。@devil缠在一个答案的评论区[6]提到:如果只是单个按键,(快捷键)基本上没有任何用处。以 V 为例:当用 Tab 到达「赞同」按钮时,直接(按)空格就可以点赞同。(另外)如果在查看内容区点击 V,焦点不会跑到「赞同」按钮上。合理的快捷键有可能用处不大,而不合理的快捷键不但不能帮助视障用户,还会帮倒忙。@殷晓波和@devil缠都提到「使用 V 赞同之后,希望可以 focus 到赞同按钮」,如果不真正使用屏幕阅读器浏览网页,是无法想象这句话的原因的:快捷键赞同后,需要转移焦点简单来说,屏幕阅读器只有在焦点改变时才会阅读焦点内的文字,它监控不到「赞同按钮变深蓝」这样普通人可以轻松理解的设计反馈。如果使用快捷键赞同后不改变焦点,连按下键盘发生了什么都不知道,也就不会知道「已经赞同了答案」。此时 tab 到赞同按钮上阅读到「已赞同」的文字才知道发生了什么就很奇怪。浏览器的点击行为会自动 focus 在可交互的元素上(例如 <button> 或 <a>),而此时按 Enter 或 Space 等快捷键可以「模拟一次点击」,这套现成的体系很容易被忽略。在实际体验中,tab 到「阅读全文」按钮再按 Space 来展开全文并不比用快捷键 O 来展开全文麻烦很多。除此之外,很多读屏软件或者视障用户也会定义、开发个性化的快捷键[6]。这么来看,使用 <button> 等语义化标签使元素可交互元素也可以被 focus、尽可能使用 <a> 而不是在监听 onClick 事件时使用 location.href 进行页面跳转、配置好 aria 属性等…对 Accessibility 更有意义。总的来说,实现快捷键和实现其他功能一样也要注意视障用户的使用、交互体验,比如:任何快捷键操作后都要像点击一样转移焦点。转移焦点后,还需要配置 aria-label 等属性来阅读出有意义的提示文字,比如赞同还是已赞同、反对还是已反对。快捷键只是 Accessibility 的一部分,而可访问性又是一个更加系统和复杂的工程。知乎做了一些努力[7],但还远远不够。也欢迎对这个领域有更多了解的朋友提出建议。Q & A可以关闭快捷键吗?是的。如果你使用 Vimperator 或者 Vimium 等浏览器扩展定制了快捷键而不想和知乎的冲突,可以在桌面端网页的个人偏好设置(https://www.zhihu.com/settings/preference)中关闭快捷键[8]。这篇文章的注释与参考是如何做到的?这是编辑器的新功能,会在开放后再行介绍。参考^这些快捷键在迁移到新版 Web 页面时没有同步迁移,在很长一段时间内都没有实现。^包括 Twitter、Facebook、Gmail 与新浪微博等,知乎从这些网站的实现细节中受益良多。^一个叫 tabbable 的库中有关于这两者区别的介绍,这个库在实现 focus trap 等效果时很有用。 https://github.com/davidtheclark/tabbable^如何高效地查找离视窗最近、滚动距离最小的元素,这个算法比较有意思,这里不赘述了。^知乎和 Facebook 与新浪微博一样,会选中视野内可见的新元素,而 Twitter 会放弃滚动。^ab根据 @devil缠 在这个答案评论区中的说法,他使用的读屏环境还会定义包括 K 下跳 10 个链接、Shift+K 上跳 10 个链接等快捷键 https://www.zhihu.com/question/19842222/answer/17152043^@长天之云 的答案介绍了一些知乎对 a11y 的支持 https://www.zhihu.com/question/20487917/answer/15265930^只在当前使用的浏览器中生效。作者:孙北吉出处:https://zhuanlan.zhihu.com/p/59928288(图片来源网络,侵删)
0 评论