使用箭头键在列表中导航?(JavaScript/JQ)

18

我似乎找不到如何实现这一功能的答案,但我已经看过多次。本质上,我正在输出一个列表,并且我想创建使用箭头键/回车键突出显示和选择这些项的功能。有人能帮助我想出如何实现吗?我知道如何使用键码(当然),只是不知道如何将其转换为在列表上选择项目的功能代码...

我认为也许我需要有某种隐藏的单选按钮来标记它是否被选中...但即使这样,我也不知道如何跳到列表上下的另一个单选按钮。如果有人能帮我一把,我会非常感激。谢谢。

4个回答

55

既然你没有详细解释你遇到的问题,我就提供了一个通用的解决方案。希望能对你有所帮助:

var li = $('li');
var liSelected;
$(window).keydown(function(e) {
    if(e.which === 40) {
        if(liSelected) {
            liSelected.removeClass('selected');
            next = liSelected.next();
            if(next.length > 0) {
                liSelected = next.addClass('selected');
            } else {
                liSelected = li.eq(0).addClass('selected');
            }
        } else {
            liSelected = li.eq(0).addClass('selected');
        }
    } else if(e.which === 38) {
        if(liSelected) {
            liSelected.removeClass('selected');
            next = liSelected.prev();
            if(next.length > 0) {
                liSelected = next.addClass('selected');
            } else {
                liSelected = li.last().addClass('selected');
            }
        } else {
            liSelected = li.last().addClass('selected');
        }
    }
});

JSFiddle: http://jsfiddle.net/Vtn5Y/

JSFiddle是一个在线代码编辑器,可用于演示HTML、CSS和JavaScript代码。上面的链接指向一个保存在JSFiddle上的demo。

那正是我遇到困难的问题,谢谢你。 - Ian
如果该元素不是链接,您如何使用键盘的“Enter”键选择该元素? - ClosDesign
1
如果(e.which === 13){$(".selected").click();} - Webby
我会在“if (e.which ===40){ e.preventDefault();.... } else if (e.which ===38){ e.preventDefault(); }”之后添加preventDefault()来防止窗口向下/向上滚动。 - Roy B. xSITE

15

我的本地JavaScript示例。

var ul = document.getElementById('list');
var liSelected;
var index = -1;

document.addEventListener('keydown', function(event) {
    var len = ul.getElementsByTagName('li').length-1;
    
    // DOWN ARROW 
    if(event.which === 40) {
        index++;

        if (liSelected) {
            removeClass(liSelected, 'selected');
            next = ul.getElementsByTagName('li')[index];

            if(typeof next !== undefined && index <= len) {
                liSelected = next;
            }
            else {
                index = 0;
                liSelected = ul.getElementsByTagName('li')[0];
            }

            addClass(liSelected, 'selected');
            console.log(index);
        }
        else {
            index = 0;
            liSelected = ul.getElementsByTagName('li')[0];
            addClass(liSelected, 'selected');
        }
    }
    // UP ARROW
    else if (event.which === 38) {
        if (liSelected) {
            removeClass(liSelected, 'selected');
            index--;
            next = ul.getElementsByTagName('li')[index];

            if(typeof next !== undefined && index >= 0) {
                liSelected = next;
            }
            else {
                index = len;
                liSelected = ul.getElementsByTagName('li')[len];
            }

            addClass(liSelected, 'selected');
        }
        else {
            index = 0;
            liSelected = ul.getElementsByTagName('li')[len];
            addClass(liSelected, 'selected');
        }
    }
}, false);

function removeClass(el, className) {
    if(el.classList) {
        el.classList.remove(className);
    } else {
        el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
    }
};

function addClass(el, className) {
    if(el.classList) {
        el.classList.add(className);
    } else {
        el.className += ' ' + className;
    }
};
li.selected {background:yellow}
<ul id="list">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
</ul>

https://jsfiddle.net/m6watqpe/


7

2020 更新

如果有人想在 Vue.js 中完成这个操作,我已经添加了下面的代码,并在必要的地方添加了注释,其余部分都很容易理解。

HTML

<script type="text/x-template" id="list">
  <div id="list-container" ref="root">
    <div v-for="item in items" :key="item.id" class="list-item" :class="item.id === selectedId ? 'selected': ''" @click="select(item.id)">
      {{item.value}}
    </div>
  </div>
</script>

<div id="app">
  <list></list>
</div>

CSS(层叠样式表)
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

html {
  height: 100%;
}

body {
  height: 100%;
  min-height: 100%;
  padding: 1rem;
  font-family: 'Tahoma', sans-serif;
}

.list-item {
  padding: 1rem 0.25rem;
}

.selected {
  background: lightyellow;
}

JS

const items = new Array(100).fill(null).map((item, index) => {
  return { id: index, value: "Item " + index };
});

// https://dev59.com/9G025IYBdhLWcg3w_a-r
function scrollIntoViewIfNeeded(target) {
    var rect = target.getBoundingClientRect();
    if (rect.bottom > window.innerHeight) {
        target.scrollIntoView(false);
    }
    if (rect.top < 0) {
        target.scrollIntoView();
    } 
}

Vue.component("list", {
  template: "#list",
  data() {
    return {
      items,
      selectedId: 0
    };
  },
  methods: {
    select(itemId) {
      this.selectedId = itemId;
      scrollIntoViewIfNeeded(this.$refs.root.children[itemId])
      // this.$refs.root.children[item.id].scrollIntoView({ behavior: "smooth" });
    },
    handleKeyDown(event) {
      switch (event.keyCode) {
        // In case of left arrow key move to the last item
        case 37:
          if (this.selectedId > 0) {
            this.select(this.selectedId - 1);
          }
          // Prevent the default scroll event from firing
          event.preventDefault();
          break;
        // In case of up arrow key, move to the last item
        case 38:
          if (this.selectedId > 0) {
            this.select(this.selectedId - 1);
          }
          event.preventDefault();
          break;
        // In case of right arrow key, move to the next item
        case 39:
          if (this.selectedId < this.items.length - 1) {
            this.select(this.selectedId + 1);
          }
          event.preventDefault();
          break;
        // In case of down arrow key, move to the next item
        case 40:
          if (this.selectedId < this.items.length - 1) {
            this.select(this.selectedId + 1);
          }
          event.preventDefault();
          break;
      }
    }
  },
  mounted() {
    window.addEventListener("keydown", this.handleKeyDown);
  },
  destroyed() {
    window.removeEventListener("keydown", this.handleKeyDown);
  }
});

new Vue({
  el: "#app"
});

1
虽然这段代码是Vue特定的,但它很容易应用到其他地方。不错! - Evan

0
这可能取决于浏览器。 只有当单选按钮输入相邻(标签也可以)时,它才能正常工作。
<input type="radio" ... /> 
<input type="radio" ... />
<input type="radio" ... />

但这将破坏Firefox和可能其他浏览器中的无线电导航:

<div><input type="radio" ... /> ... </div>
<div><input type="radio" ... /> ... </div>

当你想要创建比简单列表(如类别)更复杂的东西时,这就变得很烦人了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接