使用 jQuery 的上/下/左/右键盘导航?

Posted

技术标签:

【中文标题】使用 jQuery 的上/下/左/右键盘导航?【英文标题】:Up/Down/Left/Right keyboard navigation with jQuery? 【发布时间】:2013-06-28 13:21:40 【问题描述】:

我有一个 div 的列表,其中包含一组相同的高度/宽度,它们是 float:left,因此如果父项小于项目的组合,它们会彼此相邻并折叠。

相当标准。 这是为了创建一个 twitter 引导图标列表,它提供如下内容:

我已经使用下面的代码添加了下一个/上一个键盘导航,但是您会注意到上/下箭头键被映射为调用左/右功能。我不知道该怎么做是实际进行向上/向下导航?

JsFiddle

(function ($) 
    $.widget("ui.iconSelect", 

        // default options
        options: 

        ,

        $select: null,

        $wrapper: null,

        $list: null,

        $filter: null,

        $active: null,

        icons: ,

        keys: 
            left: 37,
            up: 38,
            right: 39,
            down: 40

        ,

        //initialization function
        _create: function () 

            var that = this;
            that.$select = that.element;

            that.$wrapper = $('<div class="select-icon" tabindex="0"></div>');
            that.$filter = $('<input class="span12" tabindex="-1" placeholder="Filter by class name..."/>').appendTo(that.$wrapper);
            that.$list = $('<div class="select-icon-list"></div>').appendTo(that.$wrapper);


            //build the list of icons
            that.element.find('option').each(function () 
                var $option = $(this);
                var icon = $option.val();

                that.icons[icon] = $('<a data-class="' + icon + '"><i class="icon ' + icon + '"></i></a>');

                if ($option.is(':selected')) 
                    that.icons[icon].addClass('selected active');
                

                that.$list.append(that.icons[icon]);
            );

            that.$wrapper.insertBefore(that.$select);
            that.$select.addClass('hide');



            that._setupArrowKeysHandler();
            that._setupClickHandler();
            that._setupFilter();
            that.focus('selected');
        ,

        focus: function (type) 
            var that = this;
            if (that.$active === null || that.$active.length == 0) 
                if (type == 'first') 
                    that.$active = that.$list.find('a:visible:first');
                 else if (type == 'last') 
                    that.$active = that.$list.find('a:visible:last');
                 else if (type == 'selected') 
                    that.$active = that.$list.find('a.selected:visible:first');
                    that.focus('first');
                
            
            that.$active.addClass('active');
            var toScroll = ((that.$list.scrollTop() + that.$active.position().top)-that.$list.height()/2)+that.$active.height()/2;
            //that.$list.scrollTop((that.$list.scrollTop() + top)-that.$list.height()/2);
            that.$list.stop(true).animate(
                scrollTop: toScroll,
                queue: false,
                easing: 'linear'
            , 200);

            if (type === 'selected') 
                return false;
            

            that.$select.val(that.$active.data('class'));
            that.$select.trigger('change');

        ,

        _setupArrowKeysHandler: function () 
            var that = this;

            that.$wrapper.on('keydown', function (e) 
                switch (e.which) 
                    case that.keys.left:
                        that.moveLeft();
                        break;
                    case that.keys.up:
                        that.moveUp();
                        break;
                    case that.keys.right:
                        that.moveRight();
                        break;
                    case that.keys.down:
                        that.moveDown();
                        break;
                    case 16:
                        return true;
                    case 9:
                        return true;
                    break;
                    default:
                        that.$filter.focus();
                        return true;
                
                return false;
            );
        ,

        _setupFilter: function()
            var that = this;

            that.$filter.on('keydown keyup keypress paste cut change', function(e)
                that.filter(that.$filter.val());
            );
        ,

        _setupClickHandler: function () 
            var that = this;
            that.$list.on('click', 'a', function () 
                that.$wrapper.focus();
                that.$active.removeClass('active');
                that.$active = $(this);
                that.focus('first');
            );
        ,

        moveUp: function () 
            var that = this;
            return that.moveLeft();
        ,

        moveDown: function () 
            var that = this;
            return that.moveRight();
        ,

        moveLeft: function () 
            var that = this;
            that.$active.removeClass('active');
            that.$active = that.$active.prevAll(':visible:first');
            that.focus('last');
            return false;
        ,

        moveRight: function () 
            var that = this;
            that.$active.removeClass('active');
            that.$active = that.$active.nextAll(':visible:first');
            that.focus('first');
            return false;
        ,

        filter: function(word)
            var that = this;
            var regexp = new RegExp(word.toLowerCase());
            var found = false;
            $.each(that.icons, function(i, $v)
                found = regexp.test(i);
                if(found && !$v.is(':visible'))
                    $v.show();
                 else if(!found && $v.is(':visible'))
                    $v.hide();
                
            );
        

    );
)(jQuery);

【问题讨论】:

您可以像每行中的项目一样经常调用左/右函数 - 这将是最简单的,甚至无需查看您的代码;)我现在就这样做......跨度> @luk2302,可能的解决方案是的,但是您如何计算行中有多少项目?我想我可以做到$wrapper.width() mod $element.width(),但整个方法对我来说似乎相当老套。我一直在研究zehnet.de/2010/11/19/…,但我无法弄清楚如何相对于包装器来说明这一点...... 只使用您在链接中提供的代码有什么问题。如果您只是粘贴代码并添加自己的代码,它应该可以工作:您可以通过 $.elementFromPoint($(that.$active).offset().left, $(that.$active).offset().top-10); 获取上面的元素。对于下面的,您必须添加 10 + 一个项目本身的高度,但这是固定的。我不知道如何更改代码中的活动属性,因此我不提供答案,只是评论。 【参考方案1】:

可能是这样的:http://jsfiddle.net/QFzCY/

var blocksPerRow = 4;

$("body").on("keydown", function(e)
    var thisIndex = $(".selected").index();
    var newIndex = null;
    if(e.keyCode === 38) 
        // up
       newIndex = thisIndex - blocksPerRow;
    
    else if(e.keyCode === 40) 
        // down
        newIndex = thisIndex + blocksPerRow;       
    
    if(newIndex !== null)  
        $(".test").eq(newIndex).addClass("selected").siblings().removeClass("selected");   
        
 );

基本上,您设置一行中有多少项,然后找到当前索引并减去或添加该数量以通过新索引选择下一个元素。

如果您需要知道每行有多少块,您可以这样做:

var offset = null;
var blocksPerRow = 0;
$(".test").each(function()
    if(offset === null) 
        offset = $(this).offset().top;
    
    else if($(this).offset().top !== offset) 
        return false;
    
    blocksPerRow++;
);

要处理您的“边缘”案例,您可以这样做:

if(newIndex >= $(".test").length) 
    newIndex = $(".test").length - newIndex;

【讨论】:

这看起来不错,但是您将如何处理“字面意思”的边缘情况,也就是如果用户在底行并按下,我们想选择顶部的相关项目行,向上也一样? 用你的“边缘”案例更新了答案。 还有一个极端情况,正如您从演示中看到的那样,我们还有一个过滤器框,所以如果用户确实过滤了项目,那么我们需要以某种方式排除这些项目......跨度> 要只计算过滤后的元素,您可以对 blocksPerRow 进行计数,并仅对可见的元素进行其他工作,例如$(".select-icon-list").children(":visible"). 所以,我可以使用$icons = $(".select-icon-list").children(":visible"),然后使用.eq(newIndex) 而不是使用$icons[newIndex],但是我将如何从该数组中提取当前活动的项目索引?我是否需要遍历数组并将其中的每个值与所选项目进行比较?【参考方案2】:
    moveUp: function () 
        var that = this;
        var index = $(this).index();
        var containerWidth = parseInt( $('.select-icon-list').innerWidth(), 10);
        var iconWidth = parseInt( $('.select-icon-list > a').width(), 10);
        var noOfCols = Math.floor( containerWidth / iconWidth );
        var newIndex = ( (index - noOfCols) < 0 ) ? index : (index - noOfCols);
        var elem = $('.select-icon-list > a')[index];
    ,

缓存保持静态的内容。

【讨论】:

这看起来不错,但是您将如何处理“字面意思”的边缘情况,也就是如果用户在底行并按下,我们想选择顶部的相关项目行,向上也一样? 我只是用( (index - noOfCols) &lt; 0 ) ? index : (index - noOfCols) 防止这种边缘情况,并保持在同一个项目上。

以上是关于使用 jQuery 的上/下/左/右键盘导航?的主要内容,如果未能解决你的问题,请参考以下文章

jQuery 虚拟键盘 - 设置左箭头和右箭头

键盘箭头的扫描码是啥? (右、左、下、上)

APP原型的设计步骤是什么?

如何为 FileMerge 分配快捷方式

到达最后一张幻灯片时,使用键盘左箭头键不应向左滚动

javascript按键盘上/右/下/左箭头加速运动