Welcome to my blog. You can post whatever you like!
The blog post is using markdown, and here is a simple guide. Markdown Guide
LOGIN TO POST A BLOG网页vim键位浏览博客及评论实现方法

Author: Satori
Posted: 2017-10-07 12:25:41
Category: CSS HTML JavaScript vim键位 技术
Views: 949 Comments: 4
## 一、背景
这个功能的想法最早来源于北邮人论坛。当时我刚会用vim,在浏览论坛的时候我不禁试了一下,结果发现真的可以用vim键位,表示很欣喜。这个功能在我刚开始开发这个博客就考虑了,但是当时还有很多其它更重要的功能要开发,结果基本上拖了半年才真正实现了这个功能,算是完成了一个心愿。网页快捷键浏览在其他网站也有,比如twitter可以用j和k来上下切换浏览。
## 二、需求分析
因为后期开发了用户界面,因此用户界面的条目,包括发表的博客,发表的评论,回复我的等等都需要能使用vim键位浏览。同时,在博客详情页面,也能通过vim键位来浏览评论。
这里需要定义一下什么叫用vim键位浏览。就是说当使用vim键位的时候,会选中目标的博客,将它移动到屏幕中央,并在外边框加上颜色进行标注。在浏览博客条目的时候,按照vim键位,`j`就是向下移,`k`就是向上移,`h`就是翻上一页,`l`就是翻下一页。
在博客详情页面中,因为评论是树形结构,因此键位的意义也就稍有不同。`j`就是按照评论树的先序遍历顺序取下一个评论,同理`k`就是取上一个。`h`定义为评论树当前结点的前一个兄弟结点,同理`l`就是下一个兄弟节点。
在键位激活的时候,要做两件事情,一件是将目标窗口标红,另一件是将屏幕移动到目标窗口。这里也就分列表页面和详情页面来讲实现方法。不过在此之前先讲这两个功能都需要实现的逻辑,也就是移动窗口和给窗口加上边框。
## 三、移动窗口及边框着色的实现方法
### 1. 移动屏幕到目标窗口
这一部分主要是实现当用户按下按键之后移动到目标窗口的方法。这里将这个操作封装为一个函数。传入目标窗口以及移动时间。难点在于怎么计算目标窗口的位置。因为是要移动到屏幕中央,因此先判断能不能移到中央。这个取决于目标块的大小。如果目标窗口的高度都比屏幕高度高了,那就不能完整显示,就只能将它移动到屏幕顶端。如果不是的话,就能移动到中央。
首先需要明白怎么样使窗口移动。这里采用的做法是让**整个页面**移动,而不是让该窗口移动。可以理解为屏幕是绝对不动的,动的只有页面,页面在屏幕里面滑动,而屏幕只显示页面当前滑动的位置。使用`scrollTop`的动画来实现整个页面的移动。这个动画需要给出位移。在这种设计下,位移多少就是整个页面上滑动多少。
举个例子,如果位移为0的话页面不移动,位移为100的话页面向上移动100px,可以想象,如果计算出某元素顶部距离页面的距离,比如距离为x,位移x就能刚好移动到那个元素的顶部,也就是屏幕顶部和元素顶部重合。
因此剩下的事情就简单了,首先算出该窗口距离顶部的位移。如果直接让页面移动那么多的话那个窗口就在屏幕顶端了,因此需要页面移动少一点,移动到窗口居中就行了,下面就算少多少距离。这个就简单了,当前窗口需要移动到屏幕底端一半的位置,也就是屏幕高度减去窗口高度除以2。只要页面少往上移动这么多距离就能使窗口刚好居中。代码如下。
```
function move_to_ele(ele, time) {
// scroll to ele
if (ele.size() === 1)
{
var elOffset = ele.offset().top;
var elHeight = ele.height();
var windowHeight = $(window).height();
var offset;
if (elHeight < windowHeight) {
offset = elOffset - ((windowHeight / 2) - (elHeight / 2));
}
else {
offset = elOffset;
}
$('html, body').animate({
scrollTop: offset
}, time);
}
}
```
### 2. 给窗口边框着色
给边框着色就比较简单了,我这里也是封装成一个函数来处理。这个函数传两个参数,一个是当前已经被选中的窗口(没有的话就是空),以及下一个被选中的窗口(可能是下面一个也可能是上面一个窗口)。
着色方法比较简单,就是把当前窗口的`kb`类给移除,给下一个窗口加上`kb`类。而`kb`类定义了样式,也就是加边框。这样通过加上或者移除这个类来实现边框着色与否。`kb`样式如下。
```
.kb {
border: solid 4px rgba(255, 0, 0, 0.6);
}
```
函数实现如下。
```
function color_to_ele(curEle, next)
{
if (next.size() === 1)
{
if (curEle !== null)
{
curEle.removeClass("kb");
}
next.addClass("kb");
}
}
```
## 四、列表的定位
### 1. 找到当前激活的窗口
首先需要找是否已经有窗口被激活了,如果没有的话按`j`从第一个开始浏览,如果有的话就从当前位置开始。我们根据div的class属性来判断是否有激活窗口,有的话就返回那个窗口。代码如下。
```
function getCurrentElement()
{
var ele = $(".blog-div.kb");
if (ele.size() != 1)
{
return null;
}
else
{
return ele;
}
}
```
### 2. 四个键位的定位实现
vim键位的`j` `k` `h` `l`分别对应下移,上移,左翻一页,右翻一页。对于上下移动,调用next方法来获取下一个激活窗口的位置,使用prev方法得到上一个激活窗口的位置。得到目标位置后,再对新窗口的边框进行着色,并移动到对应位置。对于翻页操作,只需模拟点击左翻和右翻按钮来实现。同时需要注意边界情况,比如没有上一页或者下一页,没有上一个窗口或者下一个窗口等等,代码如下。
```
function movedown(time)
{
var curEle = getCurrentElement();
var next;
// no item selected
if (curEle === null)
{
next = $(".blog-div").first();
}
else
{
next = curEle.next(".blog-div");
}
// if not the last item
color_to_ele(curEle, next);
move_to_ele(next, time);
}
function moveup(time)
{
var curEle = getCurrentElement();
// no item selected
if (curEle !== null)
{
var next = curEle.prev(".blog-div");
color_to_ele(curEle, next);
move_to_ele(next, time);
}
}
function moveprev()
{
var btn = $("#prev-btn");
if (btn.size() === 1)
{
btn[0].click();
}
}
function movenext()
{
var btn = $("#next-btn");
if (btn.size() === 1)
{
btn[0].click();
}
}
```
### 3. 绑定键盘按键
最后一步就是将四个按键和刚才的四个函数相连接,设置一个移动时间即可。最后需要注意用户在输入框打字的时候不要监听这四个快捷键。代码如下。
```
var time = 100;
var vim_key_listener = new window.keypress.Listener;
vim_key_listener.simple_combo("j", function() {
movedown(time);
});
vim_key_listener.simple_combo("k", function() {
moveup(time);
});
vim_key_listener.simple_combo("h", function() {
moveprev();
});
vim_key_listener.simple_combo("l", function() {
movenext();
});
$("input[type=text], textarea")
.bind("focus", function() { vim_key_listener.stop_listening(); })
.bind("blur", function() { vim_key_listener.listen(); });
```
### 4. 用户界面的特殊处理
因为用户界面有多个tab,每个tab的窗口类名都一样,因此需要甄别是哪个窗口被激活,保证调用`getCurrentElement`函数的时候只会得到一个元素,就是当前tab的窗口。因此需要在用户界面点击tab的时候,将其他tab中的内容全部清空,再利用ajax请求得到自己tab的内容,这样就解决这个问题了,代码如下。
```
function clear_tabs() {
$("#blog-posts").empty();
$('#comment-posts').empty();
$('#comment-received').empty();
$("#blog-subscriptions-div").empty();
}
$('a[data-toggle="pill"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href");// activated tab
clear_tabs();
});
```
## 五、评论的定位
### 1. 找到当前激活的评论
大体和前面一样的做法,只是将类名改为`comment-body`,代码如下。
```
function getCurrentElement()
{
var ele = $(".kb .comment-body").first();
if (ele.size() != = 1)
{
return null;
}
else
{
return ele;
}
}
```
### 2. 四个键位的定位实现
vim键位的`j` `k`分别对应dom树的先序遍历的下、上一个结点。`h` `l`对应同级兄弟结点的上、下一个结点。
对于先序遍历的结果的获取,使用jQuery选取所有类为`comment-body`的元素,这样获取的列表默认就是dom树先序遍历的结果,然后只需找到当前激活窗口在这个列表的位置,选取上一个或者下一个位置就能得到先序遍历的前后结点。
对于上下兄弟结点的获取,直接找到它的父节点并在父节点调用next方法获取下一个list的位置,同理prev方法得到上一个list位置,也就是找到了兄弟结点的父亲结点。然后只需找那个父亲结点的孩子就能找到下一个窗口的位置。
得到目标位置后,再对新窗口的边框进行着色,并移动到对应位置。同时需要注意边界情况,比如没有上一个或下一个先序遍历的位置,没有上一个或下一个兄弟等等,代码如下。
```
function movenext(time)
{
var curEle = getCurrentElement();
var next;
// no item selected
if (curEle == = null)
{
next = $(".comment-body").first();
color_to_ele(null, next.parent());
}
else
{
next = curEle.parent().next("li").children(".comment-body");
color_to_ele(curEle.parent(), next.parent());
}
// if not the last item
move_to_ele(next, time);
}
function moveprev(time)
{
var curEle = getCurrentElement();
var next;
// no item selected
if (curEle != = null)
{
next = curEle.parent().prev("li").children(".comment-body");
color_to_ele(curEle.parent(), next.parent());
// if not the last item
move_to_ele(next, time);
}
}
function movedown(time)
{
var curEle = getCurrentElement();
// no item selected
if (curEle == = null)
{
var next = $(".comment-body").first();
color_to_ele(null, next.parent());
move_to_ele(next, time);
}
else
{
var all_items = $(".comment-body");
var cur_ind = all_items.index($(".kb .comment-body").first());
if (cur_ind + 1 <= all_items.size() - 1)
{
var next = $(all_items.get(cur_ind + 1));
color_to_ele(curEle.parent(), next.parent());
move_to_ele(next, time);
}
}
}
function moveup(time)
{
var curEle = getCurrentElement();
// no item selected
if (curEle != = null)
{
var all_items = $(".comment-body");
var cur_ind = all_items.index($(".kb .comment-body").first());
if (cur_ind - 1 >= 0)
{
var next = $(all_items.get(cur_ind - 1));
color_to_ele(curEle.parent(), next.parent());
move_to_ele(next, time);
}
}
}
```
### 3. 绑定键盘按键
最后一步就是将四个按键和刚才的四个函数相连接,设置一个移动时间即可。最后需要注意用户在输入框打字的时候不要监听这四个快捷键。代码如下。
```
$(document).ready(function() {
var time = 100;
var vim_key_listener = new window.keypress.Listener;
vim_key_listener.simple_combo("j", function() {
movedown(time);
});
vim_key_listener.simple_combo("k", function() {
moveup(time);
});
vim_key_listener.simple_combo("h", function() {
moveprev(time);
});
vim_key_listener.simple_combo("l", function() {
movenext(time);
});
$("input[type=text], textarea")
.bind("focus", function() { vim_key_listener.stop_listening(); })
.bind("blur", function() { vim_key_listener.listen();});
});
```
至此用vim键位浏览博客列表,用户列表,博客评论的实现方法也就全部实现了。
Read More