近,知乎在桌面端网页上陆续增加了一些快捷键:
在非输入状态下按 ?(Shift+/)就可以查看快捷键列表
在开发快捷键的过程中,有一些值得分享的经验。
实现快捷键不困难,因为监听键盘事件很简单。大部分时候,我们只需要在希望响应事件的最外层元素上注册事件就行,而响应事件的回调(比如关闭模态框)也都可以在这个最外层组件内实现。
知乎首页的组件结构
但是这次的情况要更加复杂。以在首页按 V 赞同答案为例:列表内的时间线条目(FeedItem)包括了回答(AnswerItem)在内各种类型的内容,而在回答里的赞同按钮(VoteButton)才是适合响应快捷键事件的地方。在最外侧的 FeedItem 上容易注册事件却无法响应事件,在最内侧的 VoteButton 中可以响应事件却不能简单地注册事件。
如何在 React 的组件关系中优雅地实现子组件(赞同、收藏按钮等)注册快捷键和回调函数、同时直到父组件(整个回答甚至 Feed 条目)的范围内都可以响应快捷键是比较有趣的。
一种简单的想法是:放弃在子组件中注册快捷键,全部在父组件中注册快捷键,在回调的时候调用子组件的实例方法。这会造成以下这些问题:
现在知乎使用的方案是:基于 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]。下面是一些如果从零开始实现时,可能会遇到的细节问题:
当使用快捷键进行浏览时,「知道光标在哪里」很重要。屏幕阅读器可以阅读当前 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 更有意义。
总的来说,实现快捷键和实现其他功能一样也要注意视障用户的使用、交互体验,比如:
快捷键只是 Accessibility 的一部分,而可访问性又是一个更加系统和复杂的工程。知乎做了一些努力[7],但还远远不够。也欢迎对这个领域有更多了解的朋友提出建议。
是的。如果你使用 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
在的浏览器普遍都支持Javascript,客户的浏览器都安装了好几种。这里我们对javascipt做个初步的介绍。
javascript是一种基于对象和事件并具有安全性能的解释型脚本语言。不但用于编写客户端的脚本程序,由客户端浏览器解释执行。而且还可以编写在服务器端执行的脚本程序,由服务器端处理用户提交的信息并动态地向浏览器返回处理结果。
字符串型:使用单引号或者双引号括起来的一个或者多个字符串
数值型:包括整数或者浮点数(包含小数点的数或者科学计数法的数)
布尔型:true或者false
对象型:用于指定javascript程序中用到的对象
空值:给一个变量赋值null值来清除变量的内容
undefined:表示该变量尚未被赋值
1、变量命名的规则
必须以字母或者下划线开头,中间可以是数字、字母或者下划线。
变量名不能包含空格或加号、减号等符号。
变量名严格区分大小写的。大小写不一样代表不同的变量。
变量名不能使用关键字。
关键字和java的关键字类似。这里不列举。
2、声明和赋值
var variable; //声明一个变量
var variable=10; //声明并赋值11
var sum,avg; //声明多个变量
var sum=0,avg=10; //声明多个变量并赋值
Javascript的函数和语句和php有相通的地方。这里就不介绍了。
可以阅读其他相关教程:https://www.w3school.com.cn/js/index.asp
事件是某些动作发生的时候产生的信号,这些事件随时可以发生。引起事件的发生的动作成为触发事件。比如鼠标点击了某个按钮,用户在文本框输入文字都会有对应的触发事件。
下面是常见的事件:
onchange | HTML 元素已被改变 |
onclick | 用户点击了 HTML 元素 |
onmouseover | 用户把鼠标移动到 HTML 元素上 |
onmouseout | 用户把鼠标移开 HTML 元素 |
onkeydown | 用户按下键盘按键 |
onload | 浏览器已经完成页面加载 |
其他事件可以查看手册:
https://www.w3school.com.cn/js/js_events.asp
https://www.w3school.com.cn/jsref/jsref_events.asp
https://www.w3school.com.cn/jsref/dom_obj_event.asp
1、在html中嵌入javascript脚本
在html中使用<script>标记就可以嵌入到html中
<script language="javascript">
....
</script>
language标记的是设置脚本语言的名称和版本,也可以不设置该属性,浏览器默认使用javascript脚本预约进行处理。
2、在网页中引用Js文件
不管是在php,还是其他语言上甚至是普通的html页面上,都可以使用外置的脚本文件来调用javascipt脚本
<script src="url" language="javascript"></script>
src的url是js文件的路径,language的作用和上面的一样。使用脚本文件不仅可以在html中结合使用,还可以与php动态网页结合使用。使用外部js文件的优点如下:
将javascript代码从网页中独立出来,方便代码阅读
一个外部js文件可以同时被多个页面调用。需要修改代码,只需要修改js文件的代码,方便代码维护。
通过script不仅可以调用自己服务器的上面的js文件,也可以通过路径调用其他服务器上的js文件。
注意:在js文件中,只能包含javascript脚本代码,不能包含<script>标记和html代码,不然会产生错误。
在引用js文件的<script>和</script>标记的中间不能有其他的javascript代码,即使存在了浏览器也是会忽略这写代码,而只执行js文件中的javascript代码
3、页面中调用自定义函数
这个就是前面说道的事件,根据事件调用自定义函数。
<button type="button" onclick="check();"> 提交 </button>
提交按钮绑定了一个点击事件,调用check()函数。
1、开启浏览器对Javascript的支持
有些浏览器是出于安全考虑关闭了对javascript的支持,所以可以直接通过浏览器启用对javascript的支持。
2、使用注释符号验证浏览器的支持
如果用户不确定自己的浏览器是否支持javascript脚本,可以使用html的注释符号来进行验证。Html注释符号“<!--”开始,以“-->”结束。如果此注释符号里的javascript脚本在不支持javascript的浏览器,会把javascript脚本作为注释而不会显示在客户浏览器上。
<script type="text/javascript">
<!--
//即使不支持Javascript脚本,用户也看不到这里面的代码
var a={};
a[“stu”] = null;
-->
</script>
3、使用<noscript>标记验证浏览器的支持
除了上面的方式,还可以使用<noscript>标记来进行验证。
如果浏览器支持Javascript脚本,那么浏览器会忽略<noscript>...</noscript>标记中间的任何内容,反之不支持会把这标记中间的内容显示出来。用来提醒浏览者当前使用浏览器是否支持Javascript脚本。
文章有
ctrl+s一键提交的功能时候,真的是会方便很多,对于我们这些高度使用pc,并且熟悉PC快捷键的使用者来说,如何实现html响应ctrl+s快捷键,下面我分享一个简单的代码给大家。
$(window).keydown( function (event) {
if(event.ctrlKey && window.event.keyCode==83 ){
$("#submit").click()
return false
}
}) ;
当使用ctrl+s组合快捷键的时候,我们模拟了submit的点击事件进行提交文章。
*请认真填写需求信息,我们会在24小时内与您取得联系。