使用击键导航列表

Navigating a list with keystrokes

本文关键字:导航 列表      更新时间:2023-09-26

我正在创建一个项目列表,我希望用户能够使用键盘与之交互。 所以像这样的事情...

<ul contenteditable="true">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>

我的理解是,为了捕获键下/键控事件,我必须设置内容可编辑属性。 这有效,但现在 li 的内容是可编辑的,我不想要。 我只需要捕获键下/键控事件。

如何在不使内容可编辑的情况下捕获这些事件?

编辑

感谢跳转代码,以下内容可以使其保持只读...

$('ul').on('click', 'li', function (e) {
    e.preventDefault();
});

。但我仍然在 li 上留下一个闪烁的光标。 我该如何摆脱它?

编辑

开始赏金,需要对下面的预防默认答案进行以下更新。

  1. 如何摆脱闪烁的光标?
  2. 如何使不可选择? 我真的只想捕获键控事件! 如果我可以在不设置内容可编辑的情况下完成此操作,那将是可取的。
  3. 我可以在文档级别捕获键控,但问题是键控是从哪里发出的? 可能有几个插件在运行,所有插件都需要响应相同的事件,但前提是它们是活动范围。

正如您所说,您不需要编辑列表项,因此没有理由使用 contenteditable .

您需要的是一个与列表的当前活动项相对应的索引,并在按下箭头时用于递增和递减该索引的功能。下面是一个构造函数,它实现了您需要的功能:

var NavigableList = function (ul, selectedClass, activeClass) {
    var list   = null,
        index  = 0,
        walk   = function (step) {
            var walked = true;
            index += step;
            if (index < 0 || index === list.length) {
                index -= step;
                walked = false;
            }
            return walked;
        },
        active = false;
    this.active = function (state) {
        if (state !== active) {
            active = state;
            if (active) {
                ul.addClass(activeClass);
            } else {
                ul.removeClass(activeClass);
            }
        }
    };
    this.isActive = function () {
        return active;
    };
    this.down = function () {
        var previous = index,
            changed  = false;
        if (walk(1)) {
            $(list[previous]).removeClass(selectedClass);
            $(list[index]).addClass(selectedClass);
            changed = true;
        }
        return changed;
    };
    this.up = function () {
        var previous = index,
            changed  = false;
        if (walk(-1)) {
            $(list[previous]).removeClass(selectedClass);
            $(list[index]).addClass(selectedClass);
            changed = true;
        }
        return changed;
    };
    this.currentIndex = function () {
        return index;
    };
    this.currentElementx = function () {
        return $(list[index]);
    };
    ul = $(ul);
    list = ul.find('>li');
    selectedClass = selectedClass || 'current';
    activeClass = activeClass || 'active';
    $(ul).click(function (e) {
        this.active(true);
        NavigableList.activeList = this;
    }.bind(this));
    $(document).keydown(function(e) {
        var event = $.Event('change');
        if (this.isActive() && e.keyCode === 40) {
            if (this.down()) {
                event.selected = $(list[index]);
                event.index = index;
                ul.trigger(event);
            }
        } else if (this.isActive() && e.keyCode === 38) {
            if (this.up()) {
                event.selected = $(list[index]);
                event.index = index;
                ul.trigger(event);
            }
        }
    }.bind(this));
    $(list[index]).addClass(selectedClass);
};

使用它非常简单:

var list = new NavigableList($('#list'), 'current', 'active'),
    list2 = new NavigableList($('#list2'));
$(document).click(function (e) {
    if (NavigableList.activeList) {
        switch (NavigableList.activeList) {
            case list:
                list2.active(false);
                break;
            case list2:
                list.active(false);
                break;
        }
    } else {
        list.active(false);
        list2.active(false);
    }
    NavigableList.activeList = null;
});

此外,我在列表中实现了一个 change 事件,您可以通过这种方式使用:

$('#list').change(function (e) {
    console.log('List. Selected: ' + e.index);
});
$('#list2').change(function (e) {
    console.log('List 2. Selected: ' + e.index);
});

为了跨浏览器兼容性,您需要在所有Javascript代码之前使用它:

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }
    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
  };
}

这是一个工作演示。

试试这个。

<ul contenteditable="true">
   <li tabindex="1">Item 1</li>
   <li tabindex="2">Item 2</li>
   <li tabindex="3">Item 3</li>
</ul>

Tabindex 将设置 HTML 中元素的 Tab 键顺序。