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键位浏览博客及评论实现方法

Satori

Author: Satori

Posted: 2017-10-07 12:25:41

Category: CSS HTML JavaScript vim键位 技术

Views: 546 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