上一篇中我们简单实现了搜索框的功能,这节中要为它添加按键事件,“上”“下”键选择匹配的结果,“回车”键来进入下一步,以使它使用起来更加人性化。
键盘事件入口
在搜索框keyup
事件那里,针对特殊的按键做拦截(不触发搜索)。
1 | var initSearchSchool = function(instance){ |
这里定义了几个按键keyCode
的全局变量和一个阻止浏览器默认事件的方法,如下。
1 | // Constants |
这里用自己写的preventDefault
是为了能够兼容不同的浏览器,event.preventDefault()
是标准浏览器提供的,而window.event.returnValue = false
是IE下的写法。
searchSchoolChosen
是选择当前项,searchListScrollPrev
是选中上一项,而searchListScrollNext
是选中下一项,我们将在后面详细讲。
动画效果
有了上面的代码结构,接下来要做的就是为特殊按键添加效果,这里涉及到动画,又是一个蛋疼的话题。
画了一张示意图,sDiv
是父元素searchDiv
,sList
就是元素searchList
,而target
就是searchList
中具体选中的那个子元素。根据这幅图,我们有:
Δoffset = tarTop - sDivTop + scrollTop
其中父元素sDiv
上设置了height
并且overflow-y: scroll
,我们可以把sDiv
视作一个窗口,只要保证target
始终在这个窗口高度范围内即可。即随着我们按“上”“下”键,我们要保证目标子元素在这个视窗边界之内。
scrollTop <= Δoffset <= scrollTop + sDiv.height
于是我们有了控制searchDiv
滚动条动画的方法。
1 | var searchListScroll = function($searchDiv, $searchList){ |
大体看上去没有问题,但是注意到当向“下”选中时,其实是Δoffset + target.height
要在视窗范围内。因此我们作如下修正。
1 | var searchListScroll = function(isDown, $searchDiv, $searchList){ |
有了这个滚动条动画的方法,上面提到的searchListScrollPrev
和searchListScrollNext
也就信手拈来了。
1 | var searchListScrollPrev = function($searchDiv, $searchList){ |
这两个方法就是用来响应“上”“下”键,控制searchList
当前选中的子元素,为之添加class,并保证选中的元素在searchDiv
的可见范围内。
注意这里代码$cur.removeClass && $cur.removeClass('active');
这样写是因为可能找不到$cur
元素,那么$cur.removeClass
就肯定是false
了,就不会执行$cur.removeClass('active')
了。
~~还有一点要注意的是,$cur.index()
值的范围并不是0 ~ length-1
,实际上值为-1
时表示找不到元素,而超过length-1
时又会从头开始找,即$cur.index()
等于length
时其实是第一个子元素。所以这里的代码中当$cur.index() == $searchList.children().length-1
时要即时为第一个元素添加class,以保证$cur.index()
的值范围在0 ~ length-1
中。 ~~
锦上添花
1.当通过“上”“下”键来选中时,我们已经为目标子元素添加了active
的样式,那么这时如果鼠标再来捣乱该怎么办?我们只好再为鼠标添加hover
效果,以抹去上下键的选中效果。
1 | var initSearchSchool = function(instance){ |
2.至于“回车”键的响应方法,我们用最简单的办法,相当于选中的子元素click
一下。
1 | var searchSchoolChosen = function($searchList){ |
3.我们发现当我们输入关键字搜索时,其实每按一次键都执行了一次搜索和更新元素。而大多数情况下,我们输入一个关键字需要进行多次按键,比如搜索“江苏”,其实按键依次输入了“jiangsu”和最后拼音选择汉字的数字键或空格键。我们应该对此做些优化,以减少搜索执行,若使用Ajax搜索的话,可以减少很多次网络开销。
1 | var initSearchSchool = function(instance){ |
这里在全局定义常量var KEY_PRESS_INTERVAL = 300;
毫秒即可。虽然不能面面俱到,但是已经可以减少大部分按键情况的执行开销了。