百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

为知乎增加快捷键

bigegpt 2024-09-17 12:42 21 浏览

最近,知乎在桌面端网页上陆续增加了一些快捷键:

  • 在首页、个人页和搜索结果页等页面浏览时[1]:按 J、K 来选中列表中的条目,按 O 来展开或收起全文,按 V、D 来赞同与反对等。另外,按 Esc 也可以退出大部分弹窗与弹出式菜单。
  • 在播放视频时:按 F 让播放器全屏,按 M 让视频静音,按数字 0-9 可以跳转到对应的视频进度(0%-90%)等。
  • 在回答与文章的编辑器中也有一些快捷键:比如按 ?+K 可以插入链接等。把光标移动到工具栏中的按钮上可以查看介绍。

在非输入状态下按 ?(Shift+/)就可以查看快捷键列表

在开发快捷键的过程中,有一些值得分享的经验。

开发快捷键

快捷键的作用域

实现快捷键不困难,因为监听键盘事件很简单。大部分时候,我们只需要在希望响应事件的最外层元素上注册事件就行,而响应事件的回调(比如关闭模态框)也都可以在这个最外层组件内实现。

知乎首页的组件结构

但是这次的情况要更加复杂。以在首页按 V 赞同答案为例:列表内的时间线条目(FeedItem)包括了回答(AnswerItem)在内各种类型的内容,而在回答里的赞同按钮(VoteButton)才是适合响应快捷键事件的地方。在最外侧的 FeedItem 上容易注册事件却无法响应事件,在最内侧的 VoteButton 中可以响应事件却不能简单地注册事件。

如何在 React 的组件关系中优雅地实现子组件(赞同、收藏按钮等)注册快捷键和回调函数、同时直到父组件(整个回答甚至 Feed 条目)的范围内都可以响应快捷键是比较有趣的。

一种简单的想法是:放弃在子组件中注册快捷键,全部在父组件中注册快捷键,在回调的时候调用子组件的实例方法。这会造成以下这些问题:

  • 几乎放弃了组件的层级抽象:从外向里调用子组件实例方法比较 anti-pattern、难以管理(更别提在 React 中,越过接入 connect 等函数的 HOC 来获取真正的组件实例了)。
  • 快捷键会被重复定义和实现:比如在想法、文章和答案等所有引用赞同按钮的父组件中,都注册和实现同样的按 V 赞同的快捷键。
  • 在知乎的组件结构中,这种实现最多只能在位于层级中部的 AnswerItem 范围内进行,因为 FeedItem 的实现不关心子组件内部的业务。

现在知乎使用的方案是:基于 React context 来为所有子组件共享一个「快捷键实例」,在这个「作用域」下的所有子组件声明快捷键时,都会注册到这个实例上面。

参考下方的伪代码:

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

相关推荐

方差分析简介(方差分析通俗理解)

介绍方差分析(ANOVA,AnalysisofVariance)是一种广泛使用的统计方法,用于比较两个或多个组之间的均值。单因素方差分析是方差分析的一种变体,旨在检测三个或更多分类组的均值是否存在...

正如404页面所预示,猴子正成为断网元凶--吧嗒吧嗒真好吃

吧嗒吧嗒,绘图:MakiNaro你可以通过加热、冰冻、水淹、模塑、甚至压溃压力来使网络光缆硬化。但用猴子显然是不行的。光缆那新挤压成型的塑料外皮太尼玛诱人了,无法阻挡一场试吃盛宴的举行。印度政府正...

Python数据可视化:箱线图多种库画法

概念箱线图通过数据的四分位数来展示数据的分布情况。例如:数据的中心位置,数据间的离散程度,是否有异常值等。把数据从小到大进行排列并等分成四份,第一分位数(Q1),第二分位数(Q2)和第三分位数(Q3)...

多组独立(完全随机设计)样本秩和检验的SPSS操作教程及结果解读

作者/风仕在上一期,我们已经讲完了两组独立样本秩和检验的SPSS操作教程及结果解读,这期开始讲多组独立样本秩和检验,我们主要从多组独立样本秩和检验介绍、两组独立样本秩和检验使用条件及案例的SPSS操作...

方差分析 in R语言 and Excel(方差分析r语言例题)

今天来写一篇实际中比较实用的分析方法,方差分析。通过方差分析,我们可以确定组别之间的差异是否超出了由于随机因素引起的差异范围。方差分析分为单因素方差分析和多因素方差分析,这一篇先介绍一下单因素方差分析...

可视化:前端数据可视化插件大盘点 图表/图谱/地图/关系图

前端数据可视化插件大盘点图表/图谱/地图/关系图全有在大数据时代,很多时候我们需要在网页中显示数据统计报表,从而能很直观地了解数据的走向,开发人员很多时候需要使用图表来表现一些数据。随着Web技术的...

matplotlib 必知的 15 个图(matplotlib各种图)

施工专题,我已完成20篇,施工系列几乎覆盖Python完整技术栈,目标只总结实践中最实用的东西,直击问题本质,快速帮助读者们入门和进阶:1我的施工计划2数字专题3字符串专题4列表专题5流程控制专题6编...

R ggplot2常用图表绘制指南(ggplot2绘制折线图)

ggplot2是R语言中强大的数据可视化包,基于“图形语法”(GrammarofGraphics),通过分层方式构建图表。以下是常用图表命令的详细指南,涵盖基本语法、常见图表类型及示例,适合...

Python数据可视化:从Pandas基础到Seaborn高级应用

数据可视化是数据分析中不可或缺的一环,它能帮助我们直观理解数据模式和趋势。本文将全面介绍Python中最常用的三种可视化方法。Pandas内置绘图功能Pandas基于Matplotlib提供了简洁的绘...

Python 数据可视化常用命令备忘录

本文提供了一个全面的Python数据可视化备忘单,适用于探索性数据分析(EDA)。该备忘单涵盖了单变量分析、双变量分析、多变量分析、时间序列分析、文本数据分析、可视化定制以及保存与显示等内容。所...

统计图的种类(统计图的种类及特点图片)

统计图是利用几何图形或具体事物的形象和地图等形式来表现社会经济现象数量特征和数量关系的图形。以下是几种常见的统计图类型及其适用场景:1.条形图(BarChart)条形图是用矩形条的高度或长度来表示...

实测,大模型谁更懂数据可视化?(数据可视化和可视化分析的主要模型)

大家好,我是Ai学习的老章看论文时,经常看到漂亮的图表,很多不知道是用什么工具绘制的,或者很想复刻类似图表。实测,大模型LaTeX公式识别,出乎预料前文,我用Kimi、Qwen-3-235B...

通过AI提示词让Deepseek快速生成各种类型的图表制作

在数据分析和可视化领域,图表是传达信息的重要工具。然而,传统图表制作往往需要专业的软件和一定的技术知识。本文将介绍如何通过AI提示词,利用Deepseek快速生成各种类型的图表,包括柱状图、折线图、饼...

数据可视化:解析箱线图(box plot)

箱线图/盒须图(boxplot)是数据分布的图形表示,由五个摘要组成:最小值、第一四分位数(25th百分位数)、中位数、第三四分位数(75th百分位数)和最大值。箱子代表四分位距(IQR)。IQR是...

[seaborn] seaborn学习笔记1-箱形图Boxplot

1箱形图Boxplot(代码下载)Boxplot可能是最常见的图形类型之一。它能够很好表示数据中的分布规律。箱型图方框的末尾显示了上下四分位数。极线显示最高和最低值,不包括异常值。seaborn中...