Vim 热键 一旦习惯 Vim 的热键操作,网页浏览也会想用熟悉的热键操作,有人为热门浏览器开发了扩展,例如 Firefox 有个人觉得近完美的扩展解决方案 Vimperator 或 Pentadactyl,Chrome 则有不大完美的 Vichrome、Vimium 等[1] ,还有些人直接写了类 vi 的浏览器,例如 dwb[2] ,不过我想没用过 vi 的人不会去尝试这样的扩展或软件。
dwb 界面
那么,能不能直接使用 JavaScript,在网站中实现热键操作,让访问你网站的人都能享受,答案是可以的,知乎的快捷键就有些 vi 的感觉[3] 。
知乎的快捷键
功能介绍 小博在右下角弄个回到顶部按钮,对长文而言,使用鼠标慢慢滚,拉到下面,一点就回到顶部,感觉方便的,但有时候还是感觉有些多余。键盘也可用空格或 Home
End
等键,但我想说离得太远了,如若和鄙人一样使用 Poker II 等迷你键盘更是麻烦,何不采用 Vim 的热键来操作呢?使用 g
g
来返回顶部不是更方便么?所以,鄙人就想使用 JavaScript 实现这么几个简单的键绑定:
热键 操作 j
下滚一行 k
上滚一行 2
j
下滚 2 行 3
k
上滚 3 行 g
g
回到顶部 G
(Shift
+ g
) 滚到底部
注:2
j
中数字可以为任意正整数。
键盘事件 键盘操作自然离不开监听键盘事件,键盘事件有三个,分别是
事件 说明 属性 keydown 按下按键 keyCode keypress 字符键按下 charCode keyup 释放按键 keyCode
注:其中 keypress
事件是按下可输入字符的键就会触发,例如 a
、b
、c
,若按下 Ctrl
、Shift
等则不触发。
触发事件会返回对象,其中 keydown
、keyup
事件会返回 keyCode
属性,有些浏览器比如 Firefox 还会返回 key
属性,但一般还是使用兼容性比较好的 keyCode
;keypress
事件则会返回 charCode
属性。
其中 keyCode
的值可以在 MDN 找到[4] ,charCode
则区分大小写,属性值基本等同于 ASCII 码[5] 。
JavaScript Event KeyCodes[6] 这个网站可以方便获取 keyCode
值,另外可以使用代码自己获取 keyCode
:
window . addEventListener ( 'keydown' , function ( event ){ console . log ( event ) });
charCode
值则可用下面代码获得:
window . addEventListener ( 'keypress' , function ( event ){ console . log ( event ) });
说了这么多,上面所说需要用到的几个键,其 keyCode
及 charCode
如下:
按键 keyCode charCode Shift
16 无 0 - 9
48 - 57 48 - 57 G
71 71 g
71 103 j
74 106 k
75 107
注:均为数字,非字符。
实现过程 上下滚一行 看几个快捷键操作,j
、k
想必是最简单的,其余几个可能没有那么简单,所以先从简单的出发。
监听 keydown
事件,如果按下 j
, k
键,使用 scrollBy()
方法实现上滚下滚一行[7] ,假定行高为 20 个像素,那么:
function keysDown ( event ) {
if ( event . keyCode == 74 ) {
window . scrollBy ( 0 , 20 );
}
if ( event . keyCode == 75 ) {
window . scrollBy ( 0 , - 20 );
}
}
window . addEventListener ( 'keydown' , keysDown , false );
上下滚 n 行 数字的 keyCode
并不等于原数字,所以这里引入一个数组,当输入为数字时,将数组内对应值赋值为原数字。每次输入数字的时候,还要结合此前输入的数字进行计算,结合上面的上下滚一行,实现如下:
var lineHeight = 20 ; // 行高,可设置
var keys = [];
var row = 0 ;
function keysDown ( event ) {
if ( event . keyCode == 74 ) {
if ( row ) {
window . scrollBy ( 0 , lineHeight * row );
row = 0 ;
} else {
window . scrollBy ( 0 , lineHeight );
}
}
if ( event . keyCode == 75 ) {
if ( row ) {
window . scrollBy ( 0 , - lineHeight * row );
row = 0 ;
} else {
window . scrollBy ( 0 , - lineHeight );
}
}
if ( event . keyCode >= 48 && event . keyCode <= 57 ) {
for ( var i = 48 ; i <= 57 ; i ++ ) {
keys [ i ] = i - 48 ;
}
row = parseInt ( row . toString () + keys [ event . keyCode ]. toString ());
}
}
window . addEventListener ( 'keydown' , keysDown , false );
回到顶部 Vim 的普通模式下按下 g
g
,即双击 g
,就回到顶部,双击一般有一个延时的处理,虽然看起来挺简单,但凭空想象还是需要时间的,二话不说先 Google 一番,很快便在 Stack Overflow 找到了解决方法[8] ,不得不说这方法有些小聪明。
返回顶部用到了 scrollTo()
方法[9] ,具体代码可以这样写:
var inCombo = false ;
function keysDown ( event ) {
if ( event . keyCode == 71 ) {
if ( ! inCombo ) {
inCombo = true ;
// 五百毫秒的延时,即需在半秒内完成双击
setTimeout ( 'inCombo = false;' , 500 );
} else {
window . scrollTo ( 0 , 0 );
}
}
}
window . addEventListener ( 'keydown' , keysDown , false );
滚到底部 滚到底部是 Shift
+ g
,也即是组合键操作,不得不说 Google 才是人类的未来,一搜便可[10] 。
这里滚到底部需要获取整个网页的高度,其实这是个大坑,offsetHeight
、scrollHeight
傻傻分不清,各浏览器的标准也有区别,这里就随便丢了算是整个网页高度的 document.body.scrollHeight
,其实真的很想简单粗暴丢个 99999
:
var keys = [];
function keysDown ( event ) {
keys [ event . keyCode ] = true ;
if ( keys [ 16 ] && keys [ 71 ]) {
window . scrollTo ( 0 , document . body . scrollHeight );
}
}
function keysUp ( event ) {
keys [ event . keyCode ] = false ;
}
window . addEventListener ( 'keydown' , keysDown , false );
window . addEventListener ( 'keyup' , keysUp , false );
完整代码 整合以上功能,且为一些浏览器做兼容之后[11] ,完整代码如下:
var inCombo = false ;
var lineHeight = 20 ;
var keys = [];
var row = 0 ;
function keysDown ( event ) {
keys [ event . keyCode ] = true ;
if ( keys [ 16 ] && keys [ 71 ]) {
window . scrollTo ( 0 , document . body . scrollHeight );
}
if ( keys [ 71 ]) {
if ( ! inCombo ) {
inCombo = true ;
setTimeout ( 'inCombo = false;' , 500 );
} else {
window . scrollTo ( 0 , 0 );
}
}
if ( keys [ 74 ]) {
if ( row ) {
window . scrollBy ( 0 , lineHeight * row );
row = 0 ;
} else {
window . scrollBy ( 0 , lineHeight );
}
}
if ( keys [ 75 ]) {
if ( row ) {
window . scrollBy ( 0 , - lineHeight * row );
row = 0 ;
} else {
window . scrollBy ( 0 , - lineHeight );
}
}
if ( event . keyCode >= 48 && event . keyCode <= 57 ) {
for ( var i = 48 ; i <= 57 ; i ++ ) {
keys [ i ] = i - 48 ;
}
row = parseInt ( row . toString () + keys [ event . keyCode ]. toString ());
}
}
function keysUp ( event ) {
keys [ event . keyCode ] = false ;
}
if ( window . addEventListener ){
window . addEventListener ( 'keydown' , keysDown , false );
window . addEventListener ( "keyup" , keysUp , false );
} else if ( document . attachEvent ){
document . attachEvent ( 'onkeydown' , keysDown );
document . attachEvent ( 'onkeyup' , keysUp );
} else {
document . addEventListener ( 'keydown' , keysDown , false );
document . addEventListener ( "keyup" , keysUp , false );
}
上面说了那么多,原来也不需要用到 keypress
,如果做到严格一点,区分大小写,或许 keypress
就派上用场。不过我想现在大多数人的大写锁定键基本不用到,所以也没必要较劲。为啥不实现 Ctrl
+ f
, Ctrl
+ b
等?因为 Ctrl
+ *
本身就是浏览器的热键,还需考虑如何屏蔽。
另外,可以用 jQuery 简单实现平滑滚动而非直接定位,或者也可自己写,在此鄙人不想折腾了。
哦,对了,鄙人已将代码部署到本博,顺便学一下知乎,加按 c
查看评论,赶紧使用 g
g
试试吧~
参考资料 本文历史